editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_buffer_view::InvalidBufferView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  224
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([4..5])
  228        });
  229        editor.insert("e", window, cx);
  230        editor.end_transaction_at(now, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  233
  234        now += group_interval + Duration::from_millis(1);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([2..2])
  237        });
  238
  239        // Simulate an edit in another editor
  240        buffer.update(cx, |buffer, cx| {
  241            buffer.start_transaction_at(now, cx);
  242            buffer.edit([(0..1, "a")], None, cx);
  243            buffer.edit([(1..1, "b")], None, cx);
  244            buffer.end_transaction_at(now, cx);
  245        });
  246
  247        assert_eq!(editor.text(cx), "ab2cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  249
  250        // Last transaction happened past the group interval in a different editor.
  251        // Undo it individually and don't restore selections.
  252        editor.undo(&Undo, window, cx);
  253        assert_eq!(editor.text(cx), "12cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  255
  256        // First two transactions happened within the group interval in this editor.
  257        // Undo them together and restore selections.
  258        editor.undo(&Undo, window, cx);
  259        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  260        assert_eq!(editor.text(cx), "123456");
  261        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  262
  263        // Redo the first two transactions together.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "12cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  267
  268        // Redo the last transaction on its own.
  269        editor.redo(&Redo, window, cx);
  270        assert_eq!(editor.text(cx), "ab2cde6");
  271        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  272
  273        // Test empty transactions.
  274        editor.start_transaction_at(now, window, cx);
  275        editor.end_transaction_at(now, cx);
  276        editor.undo(&Undo, window, cx);
  277        assert_eq!(editor.text(cx), "12cde6");
  278    });
  279}
  280
  281#[gpui::test]
  282fn test_ime_composition(cx: &mut TestAppContext) {
  283    init_test(cx, |_| {});
  284
  285    let buffer = cx.new(|cx| {
  286        let mut buffer = language::Buffer::local("abcde", cx);
  287        // Ensure automatic grouping doesn't occur.
  288        buffer.set_group_interval(Duration::ZERO);
  289        buffer
  290    });
  291
  292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  293    cx.add_window(|window, cx| {
  294        let mut editor = build_editor(buffer.clone(), window, cx);
  295
  296        // Start a new IME composition.
  297        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  298        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  299        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  300        assert_eq!(editor.text(cx), "äbcde");
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Finalize IME composition.
  307        editor.replace_text_in_range(None, "ā", window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // IME composition edits are grouped and are undone/redone at once.
  312        editor.undo(&Default::default(), window, cx);
  313        assert_eq!(editor.text(cx), "abcde");
  314        assert_eq!(editor.marked_text_ranges(cx), None);
  315        editor.redo(&Default::default(), window, cx);
  316        assert_eq!(editor.text(cx), "ābcde");
  317        assert_eq!(editor.marked_text_ranges(cx), None);
  318
  319        // Start a new IME composition.
  320        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  324        );
  325
  326        // Undoing during an IME composition cancels it.
  327        editor.undo(&Default::default(), window, cx);
  328        assert_eq!(editor.text(cx), "ābcde");
  329        assert_eq!(editor.marked_text_ranges(cx), None);
  330
  331        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  332        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  333        assert_eq!(editor.text(cx), "ābcdè");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  337        );
  338
  339        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  340        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  341        assert_eq!(editor.text(cx), "ābcdę");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343
  344        // Start a new IME composition with multiple cursors.
  345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  346            s.select_ranges([
  347                OffsetUtf16(1)..OffsetUtf16(1),
  348                OffsetUtf16(3)..OffsetUtf16(3),
  349                OffsetUtf16(5)..OffsetUtf16(5),
  350            ])
  351        });
  352        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  353        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  354        assert_eq!(
  355            editor.marked_text_ranges(cx),
  356            Some(vec![
  357                OffsetUtf16(0)..OffsetUtf16(3),
  358                OffsetUtf16(4)..OffsetUtf16(7),
  359                OffsetUtf16(8)..OffsetUtf16(11)
  360            ])
  361        );
  362
  363        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  364        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  365        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  366        assert_eq!(
  367            editor.marked_text_ranges(cx),
  368            Some(vec![
  369                OffsetUtf16(1)..OffsetUtf16(2),
  370                OffsetUtf16(5)..OffsetUtf16(6),
  371                OffsetUtf16(9)..OffsetUtf16(10)
  372            ])
  373        );
  374
  375        // Finalize IME composition with multiple cursors.
  376        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  377        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  378        assert_eq!(editor.marked_text_ranges(cx), None);
  379
  380        editor
  381    });
  382}
  383
  384#[gpui::test]
  385fn test_selection_with_mouse(cx: &mut TestAppContext) {
  386    init_test(cx, |_| {});
  387
  388    let editor = cx.add_window(|window, cx| {
  389        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  390        build_editor(buffer, window, cx)
  391    });
  392
  393    _ = editor.update(cx, |editor, window, cx| {
  394        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  395    });
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(3), 3),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(1), 1),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.end_selection(window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(3), 3),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  457        editor.update_selection(
  458            DisplayPoint::new(DisplayRow(0), 0),
  459            0,
  460            gpui::Point::<f32>::default(),
  461            window,
  462            cx,
  463        );
  464    });
  465
  466    assert_eq!(
  467        editor
  468            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  469            .unwrap(),
  470        [
  471            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  472            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  473        ]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.end_selection(window, cx);
  478    });
  479
  480    assert_eq!(
  481        editor
  482            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  483            .unwrap(),
  484        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  485    );
  486}
  487
  488#[gpui::test]
  489fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  490    init_test(cx, |_| {});
  491
  492    let editor = cx.add_window(|window, cx| {
  493        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  494        build_editor(buffer, window, cx)
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  519            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.end_selection(window, cx);
  529    });
  530
  531    assert_eq!(
  532        editor
  533            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  534            .unwrap(),
  535        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  536    );
  537}
  538
  539#[gpui::test]
  540fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  541    init_test(cx, |_| {});
  542
  543    let editor = cx.add_window(|window, cx| {
  544        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  545        build_editor(buffer, window, cx)
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  550        assert_eq!(
  551            editor.selections.display_ranges(cx),
  552            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  553        );
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.update_selection(
  558            DisplayPoint::new(DisplayRow(3), 3),
  559            0,
  560            gpui::Point::<f32>::default(),
  561            window,
  562            cx,
  563        );
  564        assert_eq!(
  565            editor.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  567        );
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.cancel(&Cancel, window, cx);
  572        editor.update_selection(
  573            DisplayPoint::new(DisplayRow(1), 1),
  574            0,
  575            gpui::Point::<f32>::default(),
  576            window,
  577            cx,
  578        );
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  582        );
  583    });
  584}
  585
  586#[gpui::test]
  587fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601
  602        editor.move_down(&Default::default(), window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  606        );
  607
  608        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  612        );
  613
  614        editor.move_up(&Default::default(), window, cx);
  615        assert_eq!(
  616            editor.selections.display_ranges(cx),
  617            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  618        );
  619    });
  620}
  621
  622#[gpui::test]
  623fn test_extending_selection(cx: &mut TestAppContext) {
  624    init_test(cx, |_| {});
  625
  626    let editor = cx.add_window(|window, cx| {
  627        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  628        build_editor(buffer, window, cx)
  629    });
  630
  631    _ = editor.update(cx, |editor, window, cx| {
  632        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  633        editor.end_selection(window, cx);
  634        assert_eq!(
  635            editor.selections.display_ranges(cx),
  636            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  637        );
  638
  639        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  640        editor.end_selection(window, cx);
  641        assert_eq!(
  642            editor.selections.display_ranges(cx),
  643            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  644        );
  645
  646        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  647        editor.end_selection(window, cx);
  648        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  649        assert_eq!(
  650            editor.selections.display_ranges(cx),
  651            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  652        );
  653
  654        editor.update_selection(
  655            DisplayPoint::new(DisplayRow(0), 1),
  656            0,
  657            gpui::Point::<f32>::default(),
  658            window,
  659            cx,
  660        );
  661        editor.end_selection(window, cx);
  662        assert_eq!(
  663            editor.selections.display_ranges(cx),
  664            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  665        );
  666
  667        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  668        editor.end_selection(window, cx);
  669        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  670        editor.end_selection(window, cx);
  671        assert_eq!(
  672            editor.selections.display_ranges(cx),
  673            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  674        );
  675
  676        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  677        assert_eq!(
  678            editor.selections.display_ranges(cx),
  679            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  680        );
  681
  682        editor.update_selection(
  683            DisplayPoint::new(DisplayRow(0), 6),
  684            0,
  685            gpui::Point::<f32>::default(),
  686            window,
  687            cx,
  688        );
  689        assert_eq!(
  690            editor.selections.display_ranges(cx),
  691            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  692        );
  693
  694        editor.update_selection(
  695            DisplayPoint::new(DisplayRow(0), 1),
  696            0,
  697            gpui::Point::<f32>::default(),
  698            window,
  699            cx,
  700        );
  701        editor.end_selection(window, cx);
  702        assert_eq!(
  703            editor.selections.display_ranges(cx),
  704            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  705        );
  706    });
  707}
  708
  709#[gpui::test]
  710fn test_clone(cx: &mut TestAppContext) {
  711    init_test(cx, |_| {});
  712
  713    let (text, selection_ranges) = marked_text_ranges(
  714        indoc! {"
  715            one
  716            two
  717            threeˇ
  718            four
  719            fiveˇ
  720        "},
  721        true,
  722    );
  723
  724    let editor = cx.add_window(|window, cx| {
  725        let buffer = MultiBuffer::build_simple(&text, cx);
  726        build_editor(buffer, window, cx)
  727    });
  728
  729    _ = editor.update(cx, |editor, window, cx| {
  730        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  731            s.select_ranges(selection_ranges.clone())
  732        });
  733        editor.fold_creases(
  734            vec![
  735                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  736                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  737            ],
  738            true,
  739            window,
  740            cx,
  741        );
  742    });
  743
  744    let cloned_editor = editor
  745        .update(cx, |editor, _, cx| {
  746            cx.open_window(Default::default(), |window, cx| {
  747                cx.new(|cx| editor.clone(window, cx))
  748            })
  749        })
  750        .unwrap()
  751        .unwrap();
  752
  753    let snapshot = editor
  754        .update(cx, |e, window, cx| e.snapshot(window, cx))
  755        .unwrap();
  756    let cloned_snapshot = cloned_editor
  757        .update(cx, |e, window, cx| e.snapshot(window, cx))
  758        .unwrap();
  759
  760    assert_eq!(
  761        cloned_editor
  762            .update(cx, |e, _, cx| e.display_text(cx))
  763            .unwrap(),
  764        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  765    );
  766    assert_eq!(
  767        cloned_snapshot
  768            .folds_in_range(0..text.len())
  769            .collect::<Vec<_>>(),
  770        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  771    );
  772    assert_set_eq!(
  773        cloned_editor
  774            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  775            .unwrap(),
  776        editor
  777            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  778            .unwrap()
  779    );
  780    assert_set_eq!(
  781        cloned_editor
  782            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  783            .unwrap(),
  784        editor
  785            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  786            .unwrap()
  787    );
  788}
  789
  790#[gpui::test]
  791async fn test_navigation_history(cx: &mut TestAppContext) {
  792    init_test(cx, |_| {});
  793
  794    use workspace::item::Item;
  795
  796    let fs = FakeFs::new(cx.executor());
  797    let project = Project::test(fs, [], cx).await;
  798    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  799    let pane = workspace
  800        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  801        .unwrap();
  802
  803    _ = workspace.update(cx, |_v, window, cx| {
  804        cx.new(|cx| {
  805            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  806            let mut editor = build_editor(buffer, window, cx);
  807            let handle = cx.entity();
  808            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  809
  810            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  811                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  812            }
  813
  814            // Move the cursor a small distance.
  815            // Nothing is added to the navigation history.
  816            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  817                s.select_display_ranges([
  818                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  819                ])
  820            });
  821            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  822                s.select_display_ranges([
  823                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  824                ])
  825            });
  826            assert!(pop_history(&mut editor, cx).is_none());
  827
  828            // Move the cursor a large distance.
  829            // The history can jump back to the previous position.
  830            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  831                s.select_display_ranges([
  832                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  833                ])
  834            });
  835            let nav_entry = pop_history(&mut editor, cx).unwrap();
  836            editor.navigate(nav_entry.data.unwrap(), window, cx);
  837            assert_eq!(nav_entry.item.id(), cx.entity_id());
  838            assert_eq!(
  839                editor.selections.display_ranges(cx),
  840                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  841            );
  842            assert!(pop_history(&mut editor, cx).is_none());
  843
  844            // Move the cursor a small distance via the mouse.
  845            // Nothing is added to the navigation history.
  846            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  847            editor.end_selection(window, cx);
  848            assert_eq!(
  849                editor.selections.display_ranges(cx),
  850                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  851            );
  852            assert!(pop_history(&mut editor, cx).is_none());
  853
  854            // Move the cursor a large distance via the mouse.
  855            // The history can jump back to the previous position.
  856            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  857            editor.end_selection(window, cx);
  858            assert_eq!(
  859                editor.selections.display_ranges(cx),
  860                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  861            );
  862            let nav_entry = pop_history(&mut editor, cx).unwrap();
  863            editor.navigate(nav_entry.data.unwrap(), window, cx);
  864            assert_eq!(nav_entry.item.id(), cx.entity_id());
  865            assert_eq!(
  866                editor.selections.display_ranges(cx),
  867                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  868            );
  869            assert!(pop_history(&mut editor, cx).is_none());
  870
  871            // Set scroll position to check later
  872            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  873            let original_scroll_position = editor.scroll_manager.anchor();
  874
  875            // Jump to the end of the document and adjust scroll
  876            editor.move_to_end(&MoveToEnd, window, cx);
  877            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  878            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  879
  880            let nav_entry = pop_history(&mut editor, cx).unwrap();
  881            editor.navigate(nav_entry.data.unwrap(), window, cx);
  882            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  883
  884            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  885            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  886            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  887            let invalid_point = Point::new(9999, 0);
  888            editor.navigate(
  889                Box::new(NavigationData {
  890                    cursor_anchor: invalid_anchor,
  891                    cursor_position: invalid_point,
  892                    scroll_anchor: ScrollAnchor {
  893                        anchor: invalid_anchor,
  894                        offset: Default::default(),
  895                    },
  896                    scroll_top_row: invalid_point.row,
  897                }),
  898                window,
  899                cx,
  900            );
  901            assert_eq!(
  902                editor.selections.display_ranges(cx),
  903                &[editor.max_point(cx)..editor.max_point(cx)]
  904            );
  905            assert_eq!(
  906                editor.scroll_position(cx),
  907                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  908            );
  909
  910            editor
  911        })
  912    });
  913}
  914
  915#[gpui::test]
  916fn test_cancel(cx: &mut TestAppContext) {
  917    init_test(cx, |_| {});
  918
  919    let editor = cx.add_window(|window, cx| {
  920        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  921        build_editor(buffer, window, cx)
  922    });
  923
  924    _ = editor.update(cx, |editor, window, cx| {
  925        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  926        editor.update_selection(
  927            DisplayPoint::new(DisplayRow(1), 1),
  928            0,
  929            gpui::Point::<f32>::default(),
  930            window,
  931            cx,
  932        );
  933        editor.end_selection(window, cx);
  934
  935        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  936        editor.update_selection(
  937            DisplayPoint::new(DisplayRow(0), 3),
  938            0,
  939            gpui::Point::<f32>::default(),
  940            window,
  941            cx,
  942        );
  943        editor.end_selection(window, cx);
  944        assert_eq!(
  945            editor.selections.display_ranges(cx),
  946            [
  947                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  948                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  949            ]
  950        );
  951    });
  952
  953    _ = editor.update(cx, |editor, window, cx| {
  954        editor.cancel(&Cancel, window, cx);
  955        assert_eq!(
  956            editor.selections.display_ranges(cx),
  957            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  958        );
  959    });
  960
  961    _ = editor.update(cx, |editor, window, cx| {
  962        editor.cancel(&Cancel, window, cx);
  963        assert_eq!(
  964            editor.selections.display_ranges(cx),
  965            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                impl Foo {
  978                    // Hello!
  979
  980                    fn a() {
  981                        1
  982                    }
  983
  984                    fn b() {
  985                        2
  986                    }
  987
  988                    fn c() {
  989                        3
  990                    }
  991                }
  992            "
  993            .unindent(),
  994            cx,
  995        );
  996        build_editor(buffer, window, cx)
  997    });
  998
  999    _ = editor.update(cx, |editor, window, cx| {
 1000        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1001            s.select_display_ranges([
 1002                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1003            ]);
 1004        });
 1005        editor.fold(&Fold, window, cx);
 1006        assert_eq!(
 1007            editor.display_text(cx),
 1008            "
 1009                impl Foo {
 1010                    // Hello!
 1011
 1012                    fn a() {
 1013                        1
 1014                    }
 1015
 1016                    fn b() {⋯
 1017                    }
 1018
 1019                    fn c() {⋯
 1020                    }
 1021                }
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.fold(&Fold, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                impl Foo {⋯
 1031                }
 1032            "
 1033            .unindent(),
 1034        );
 1035
 1036        editor.unfold_lines(&UnfoldLines, window, cx);
 1037        assert_eq!(
 1038            editor.display_text(cx),
 1039            "
 1040                impl Foo {
 1041                    // Hello!
 1042
 1043                    fn a() {
 1044                        1
 1045                    }
 1046
 1047                    fn b() {⋯
 1048                    }
 1049
 1050                    fn c() {⋯
 1051                    }
 1052                }
 1053            "
 1054            .unindent(),
 1055        );
 1056
 1057        editor.unfold_lines(&UnfoldLines, window, cx);
 1058        assert_eq!(
 1059            editor.display_text(cx),
 1060            editor.buffer.read(cx).read(cx).text()
 1061        );
 1062    });
 1063}
 1064
 1065#[gpui::test]
 1066fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1067    init_test(cx, |_| {});
 1068
 1069    let editor = cx.add_window(|window, cx| {
 1070        let buffer = MultiBuffer::build_simple(
 1071            &"
 1072                class Foo:
 1073                    # Hello!
 1074
 1075                    def a():
 1076                        print(1)
 1077
 1078                    def b():
 1079                        print(2)
 1080
 1081                    def c():
 1082                        print(3)
 1083            "
 1084            .unindent(),
 1085            cx,
 1086        );
 1087        build_editor(buffer, window, cx)
 1088    });
 1089
 1090    _ = editor.update(cx, |editor, window, cx| {
 1091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1092            s.select_display_ranges([
 1093                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1094            ]);
 1095        });
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                class Foo:
 1101                    # Hello!
 1102
 1103                    def a():
 1104                        print(1)
 1105
 1106                    def b():⋯
 1107
 1108                    def c():⋯
 1109            "
 1110            .unindent(),
 1111        );
 1112
 1113        editor.fold(&Fold, window, cx);
 1114        assert_eq!(
 1115            editor.display_text(cx),
 1116            "
 1117                class Foo:⋯
 1118            "
 1119            .unindent(),
 1120        );
 1121
 1122        editor.unfold_lines(&UnfoldLines, window, cx);
 1123        assert_eq!(
 1124            editor.display_text(cx),
 1125            "
 1126                class Foo:
 1127                    # Hello!
 1128
 1129                    def a():
 1130                        print(1)
 1131
 1132                    def b():⋯
 1133
 1134                    def c():⋯
 1135            "
 1136            .unindent(),
 1137        );
 1138
 1139        editor.unfold_lines(&UnfoldLines, window, cx);
 1140        assert_eq!(
 1141            editor.display_text(cx),
 1142            editor.buffer.read(cx).read(cx).text()
 1143        );
 1144    });
 1145}
 1146
 1147#[gpui::test]
 1148fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1149    init_test(cx, |_| {});
 1150
 1151    let editor = cx.add_window(|window, cx| {
 1152        let buffer = MultiBuffer::build_simple(
 1153            &"
 1154                class Foo:
 1155                    # Hello!
 1156
 1157                    def a():
 1158                        print(1)
 1159
 1160                    def b():
 1161                        print(2)
 1162
 1163
 1164                    def c():
 1165                        print(3)
 1166
 1167
 1168            "
 1169            .unindent(),
 1170            cx,
 1171        );
 1172        build_editor(buffer, window, cx)
 1173    });
 1174
 1175    _ = editor.update(cx, |editor, window, cx| {
 1176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1177            s.select_display_ranges([
 1178                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1179            ]);
 1180        });
 1181        editor.fold(&Fold, window, cx);
 1182        assert_eq!(
 1183            editor.display_text(cx),
 1184            "
 1185                class Foo:
 1186                    # Hello!
 1187
 1188                    def a():
 1189                        print(1)
 1190
 1191                    def b():⋯
 1192
 1193
 1194                    def c():⋯
 1195
 1196
 1197            "
 1198            .unindent(),
 1199        );
 1200
 1201        editor.fold(&Fold, window, cx);
 1202        assert_eq!(
 1203            editor.display_text(cx),
 1204            "
 1205                class Foo:⋯
 1206
 1207
 1208            "
 1209            .unindent(),
 1210        );
 1211
 1212        editor.unfold_lines(&UnfoldLines, window, cx);
 1213        assert_eq!(
 1214            editor.display_text(cx),
 1215            "
 1216                class Foo:
 1217                    # Hello!
 1218
 1219                    def a():
 1220                        print(1)
 1221
 1222                    def b():⋯
 1223
 1224
 1225                    def c():⋯
 1226
 1227
 1228            "
 1229            .unindent(),
 1230        );
 1231
 1232        editor.unfold_lines(&UnfoldLines, window, cx);
 1233        assert_eq!(
 1234            editor.display_text(cx),
 1235            editor.buffer.read(cx).read(cx).text()
 1236        );
 1237    });
 1238}
 1239
 1240#[gpui::test]
 1241fn test_fold_at_level(cx: &mut TestAppContext) {
 1242    init_test(cx, |_| {});
 1243
 1244    let editor = cx.add_window(|window, cx| {
 1245        let buffer = MultiBuffer::build_simple(
 1246            &"
 1247                class Foo:
 1248                    # Hello!
 1249
 1250                    def a():
 1251                        print(1)
 1252
 1253                    def b():
 1254                        print(2)
 1255
 1256
 1257                class Bar:
 1258                    # World!
 1259
 1260                    def a():
 1261                        print(1)
 1262
 1263                    def b():
 1264                        print(2)
 1265
 1266
 1267            "
 1268            .unindent(),
 1269            cx,
 1270        );
 1271        build_editor(buffer, window, cx)
 1272    });
 1273
 1274    _ = editor.update(cx, |editor, window, cx| {
 1275        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1276        assert_eq!(
 1277            editor.display_text(cx),
 1278            "
 1279                class Foo:
 1280                    # Hello!
 1281
 1282                    def a():⋯
 1283
 1284                    def b():⋯
 1285
 1286
 1287                class Bar:
 1288                    # World!
 1289
 1290                    def a():⋯
 1291
 1292                    def b():⋯
 1293
 1294
 1295            "
 1296            .unindent(),
 1297        );
 1298
 1299        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1300        assert_eq!(
 1301            editor.display_text(cx),
 1302            "
 1303                class Foo:⋯
 1304
 1305
 1306                class Bar:⋯
 1307
 1308
 1309            "
 1310            .unindent(),
 1311        );
 1312
 1313        editor.unfold_all(&UnfoldAll, window, cx);
 1314        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1315        assert_eq!(
 1316            editor.display_text(cx),
 1317            "
 1318                class Foo:
 1319                    # Hello!
 1320
 1321                    def a():
 1322                        print(1)
 1323
 1324                    def b():
 1325                        print(2)
 1326
 1327
 1328                class Bar:
 1329                    # World!
 1330
 1331                    def a():
 1332                        print(1)
 1333
 1334                    def b():
 1335                        print(2)
 1336
 1337
 1338            "
 1339            .unindent(),
 1340        );
 1341
 1342        assert_eq!(
 1343            editor.display_text(cx),
 1344            editor.buffer.read(cx).read(cx).text()
 1345        );
 1346        let (_, positions) = marked_text_ranges(
 1347            &"
 1348                       class Foo:
 1349                           # Hello!
 1350
 1351                           def a():
 1352                              print(1)
 1353
 1354                           def b():
 1355                               p«riˇ»nt(2)
 1356
 1357
 1358                       class Bar:
 1359                           # World!
 1360
 1361                           def a():
 1362                               «ˇprint(1)
 1363
 1364                           def b():
 1365                               print(2)»
 1366
 1367
 1368                   "
 1369            .unindent(),
 1370            true,
 1371        );
 1372
 1373        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1374            s.select_ranges(positions)
 1375        });
 1376
 1377        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1378        assert_eq!(
 1379            editor.display_text(cx),
 1380            "
 1381                class Foo:
 1382                    # Hello!
 1383
 1384                    def a():⋯
 1385
 1386                    def b():
 1387                        print(2)
 1388
 1389
 1390                class Bar:
 1391                    # World!
 1392
 1393                    def a():
 1394                        print(1)
 1395
 1396                    def b():
 1397                        print(2)
 1398
 1399
 1400            "
 1401            .unindent(),
 1402        );
 1403    });
 1404}
 1405
 1406#[gpui::test]
 1407fn test_move_cursor(cx: &mut TestAppContext) {
 1408    init_test(cx, |_| {});
 1409
 1410    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1411    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1412
 1413    buffer.update(cx, |buffer, cx| {
 1414        buffer.edit(
 1415            vec![
 1416                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1417                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1418            ],
 1419            None,
 1420            cx,
 1421        );
 1422    });
 1423    _ = editor.update(cx, |editor, window, cx| {
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1427        );
 1428
 1429        editor.move_down(&MoveDown, window, cx);
 1430        assert_eq!(
 1431            editor.selections.display_ranges(cx),
 1432            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1433        );
 1434
 1435        editor.move_right(&MoveRight, window, cx);
 1436        assert_eq!(
 1437            editor.selections.display_ranges(cx),
 1438            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1439        );
 1440
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1445        );
 1446
 1447        editor.move_up(&MoveUp, window, cx);
 1448        assert_eq!(
 1449            editor.selections.display_ranges(cx),
 1450            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1451        );
 1452
 1453        editor.move_to_end(&MoveToEnd, window, cx);
 1454        assert_eq!(
 1455            editor.selections.display_ranges(cx),
 1456            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1457        );
 1458
 1459        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1460        assert_eq!(
 1461            editor.selections.display_ranges(cx),
 1462            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1463        );
 1464
 1465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1466            s.select_display_ranges([
 1467                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1468            ]);
 1469        });
 1470        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1474        );
 1475
 1476        editor.select_to_end(&SelectToEnd, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1480        );
 1481    });
 1482}
 1483
 1484#[gpui::test]
 1485fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1486    init_test(cx, |_| {});
 1487
 1488    let editor = cx.add_window(|window, cx| {
 1489        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1490        build_editor(buffer, window, cx)
 1491    });
 1492
 1493    assert_eq!('🟥'.len_utf8(), 4);
 1494    assert_eq!('α'.len_utf8(), 2);
 1495
 1496    _ = editor.update(cx, |editor, window, cx| {
 1497        editor.fold_creases(
 1498            vec![
 1499                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1500                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1501                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1502            ],
 1503            true,
 1504            window,
 1505            cx,
 1506        );
 1507        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1508
 1509        editor.move_right(&MoveRight, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(0, "🟥".len())]
 1513        );
 1514        editor.move_right(&MoveRight, window, cx);
 1515        assert_eq!(
 1516            editor.selections.display_ranges(cx),
 1517            &[empty_range(0, "🟥🟧".len())]
 1518        );
 1519        editor.move_right(&MoveRight, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(0, "🟥🟧⋯".len())]
 1523        );
 1524
 1525        editor.move_down(&MoveDown, window, cx);
 1526        assert_eq!(
 1527            editor.selections.display_ranges(cx),
 1528            &[empty_range(1, "ab⋯e".len())]
 1529        );
 1530        editor.move_left(&MoveLeft, window, cx);
 1531        assert_eq!(
 1532            editor.selections.display_ranges(cx),
 1533            &[empty_range(1, "ab⋯".len())]
 1534        );
 1535        editor.move_left(&MoveLeft, window, cx);
 1536        assert_eq!(
 1537            editor.selections.display_ranges(cx),
 1538            &[empty_range(1, "ab".len())]
 1539        );
 1540        editor.move_left(&MoveLeft, window, cx);
 1541        assert_eq!(
 1542            editor.selections.display_ranges(cx),
 1543            &[empty_range(1, "a".len())]
 1544        );
 1545
 1546        editor.move_down(&MoveDown, window, cx);
 1547        assert_eq!(
 1548            editor.selections.display_ranges(cx),
 1549            &[empty_range(2, "α".len())]
 1550        );
 1551        editor.move_right(&MoveRight, window, cx);
 1552        assert_eq!(
 1553            editor.selections.display_ranges(cx),
 1554            &[empty_range(2, "αβ".len())]
 1555        );
 1556        editor.move_right(&MoveRight, window, cx);
 1557        assert_eq!(
 1558            editor.selections.display_ranges(cx),
 1559            &[empty_range(2, "αβ⋯".len())]
 1560        );
 1561        editor.move_right(&MoveRight, window, cx);
 1562        assert_eq!(
 1563            editor.selections.display_ranges(cx),
 1564            &[empty_range(2, "αβ⋯ε".len())]
 1565        );
 1566
 1567        editor.move_up(&MoveUp, window, cx);
 1568        assert_eq!(
 1569            editor.selections.display_ranges(cx),
 1570            &[empty_range(1, "ab⋯e".len())]
 1571        );
 1572        editor.move_down(&MoveDown, window, cx);
 1573        assert_eq!(
 1574            editor.selections.display_ranges(cx),
 1575            &[empty_range(2, "αβ⋯ε".len())]
 1576        );
 1577        editor.move_up(&MoveUp, window, cx);
 1578        assert_eq!(
 1579            editor.selections.display_ranges(cx),
 1580            &[empty_range(1, "ab⋯e".len())]
 1581        );
 1582
 1583        editor.move_up(&MoveUp, window, cx);
 1584        assert_eq!(
 1585            editor.selections.display_ranges(cx),
 1586            &[empty_range(0, "🟥🟧".len())]
 1587        );
 1588        editor.move_left(&MoveLeft, window, cx);
 1589        assert_eq!(
 1590            editor.selections.display_ranges(cx),
 1591            &[empty_range(0, "🟥".len())]
 1592        );
 1593        editor.move_left(&MoveLeft, window, cx);
 1594        assert_eq!(
 1595            editor.selections.display_ranges(cx),
 1596            &[empty_range(0, "".len())]
 1597        );
 1598    });
 1599}
 1600
 1601#[gpui::test]
 1602fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1603    init_test(cx, |_| {});
 1604
 1605    let editor = cx.add_window(|window, cx| {
 1606        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1607        build_editor(buffer, window, cx)
 1608    });
 1609    _ = editor.update(cx, |editor, window, cx| {
 1610        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1611            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1612        });
 1613
 1614        // moving above start of document should move selection to start of document,
 1615        // but the next move down should still be at the original goal_x
 1616        editor.move_up(&MoveUp, window, cx);
 1617        assert_eq!(
 1618            editor.selections.display_ranges(cx),
 1619            &[empty_range(0, "".len())]
 1620        );
 1621
 1622        editor.move_down(&MoveDown, window, cx);
 1623        assert_eq!(
 1624            editor.selections.display_ranges(cx),
 1625            &[empty_range(1, "abcd".len())]
 1626        );
 1627
 1628        editor.move_down(&MoveDown, window, cx);
 1629        assert_eq!(
 1630            editor.selections.display_ranges(cx),
 1631            &[empty_range(2, "αβγ".len())]
 1632        );
 1633
 1634        editor.move_down(&MoveDown, window, cx);
 1635        assert_eq!(
 1636            editor.selections.display_ranges(cx),
 1637            &[empty_range(3, "abcd".len())]
 1638        );
 1639
 1640        editor.move_down(&MoveDown, window, cx);
 1641        assert_eq!(
 1642            editor.selections.display_ranges(cx),
 1643            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1644        );
 1645
 1646        // moving past end of document should not change goal_x
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[empty_range(5, "".len())]
 1651        );
 1652
 1653        editor.move_down(&MoveDown, window, cx);
 1654        assert_eq!(
 1655            editor.selections.display_ranges(cx),
 1656            &[empty_range(5, "".len())]
 1657        );
 1658
 1659        editor.move_up(&MoveUp, window, cx);
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1663        );
 1664
 1665        editor.move_up(&MoveUp, window, cx);
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[empty_range(3, "abcd".len())]
 1669        );
 1670
 1671        editor.move_up(&MoveUp, window, cx);
 1672        assert_eq!(
 1673            editor.selections.display_ranges(cx),
 1674            &[empty_range(2, "αβγ".len())]
 1675        );
 1676    });
 1677}
 1678
 1679#[gpui::test]
 1680fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1681    init_test(cx, |_| {});
 1682    let move_to_beg = MoveToBeginningOfLine {
 1683        stop_at_soft_wraps: true,
 1684        stop_at_indent: true,
 1685    };
 1686
 1687    let delete_to_beg = DeleteToBeginningOfLine {
 1688        stop_at_indent: false,
 1689    };
 1690
 1691    let move_to_end = MoveToEndOfLine {
 1692        stop_at_soft_wraps: true,
 1693    };
 1694
 1695    let editor = cx.add_window(|window, cx| {
 1696        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1697        build_editor(buffer, window, cx)
 1698    });
 1699    _ = editor.update(cx, |editor, window, cx| {
 1700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1701            s.select_display_ranges([
 1702                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1703                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1704            ]);
 1705        });
 1706    });
 1707
 1708    _ = editor.update(cx, |editor, window, cx| {
 1709        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1710        assert_eq!(
 1711            editor.selections.display_ranges(cx),
 1712            &[
 1713                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1714                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1715            ]
 1716        );
 1717    });
 1718
 1719    _ = editor.update(cx, |editor, window, cx| {
 1720        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1721        assert_eq!(
 1722            editor.selections.display_ranges(cx),
 1723            &[
 1724                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1725                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1726            ]
 1727        );
 1728    });
 1729
 1730    _ = editor.update(cx, |editor, window, cx| {
 1731        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1732        assert_eq!(
 1733            editor.selections.display_ranges(cx),
 1734            &[
 1735                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1736                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1737            ]
 1738        );
 1739    });
 1740
 1741    _ = editor.update(cx, |editor, window, cx| {
 1742        editor.move_to_end_of_line(&move_to_end, window, cx);
 1743        assert_eq!(
 1744            editor.selections.display_ranges(cx),
 1745            &[
 1746                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1747                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1748            ]
 1749        );
 1750    });
 1751
 1752    // Moving to the end of line again is a no-op.
 1753    _ = editor.update(cx, |editor, window, cx| {
 1754        editor.move_to_end_of_line(&move_to_end, window, cx);
 1755        assert_eq!(
 1756            editor.selections.display_ranges(cx),
 1757            &[
 1758                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1759                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1760            ]
 1761        );
 1762    });
 1763
 1764    _ = editor.update(cx, |editor, window, cx| {
 1765        editor.move_left(&MoveLeft, window, cx);
 1766        editor.select_to_beginning_of_line(
 1767            &SelectToBeginningOfLine {
 1768                stop_at_soft_wraps: true,
 1769                stop_at_indent: true,
 1770            },
 1771            window,
 1772            cx,
 1773        );
 1774        assert_eq!(
 1775            editor.selections.display_ranges(cx),
 1776            &[
 1777                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1778                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1779            ]
 1780        );
 1781    });
 1782
 1783    _ = editor.update(cx, |editor, window, cx| {
 1784        editor.select_to_beginning_of_line(
 1785            &SelectToBeginningOfLine {
 1786                stop_at_soft_wraps: true,
 1787                stop_at_indent: true,
 1788            },
 1789            window,
 1790            cx,
 1791        );
 1792        assert_eq!(
 1793            editor.selections.display_ranges(cx),
 1794            &[
 1795                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1796                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1797            ]
 1798        );
 1799    });
 1800
 1801    _ = editor.update(cx, |editor, window, cx| {
 1802        editor.select_to_beginning_of_line(
 1803            &SelectToBeginningOfLine {
 1804                stop_at_soft_wraps: true,
 1805                stop_at_indent: true,
 1806            },
 1807            window,
 1808            cx,
 1809        );
 1810        assert_eq!(
 1811            editor.selections.display_ranges(cx),
 1812            &[
 1813                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1814                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1815            ]
 1816        );
 1817    });
 1818
 1819    _ = editor.update(cx, |editor, window, cx| {
 1820        editor.select_to_end_of_line(
 1821            &SelectToEndOfLine {
 1822                stop_at_soft_wraps: true,
 1823            },
 1824            window,
 1825            cx,
 1826        );
 1827        assert_eq!(
 1828            editor.selections.display_ranges(cx),
 1829            &[
 1830                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1831                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1832            ]
 1833        );
 1834    });
 1835
 1836    _ = editor.update(cx, |editor, window, cx| {
 1837        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1838        assert_eq!(editor.display_text(cx), "ab\n  de");
 1839        assert_eq!(
 1840            editor.selections.display_ranges(cx),
 1841            &[
 1842                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1843                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1844            ]
 1845        );
 1846    });
 1847
 1848    _ = editor.update(cx, |editor, window, cx| {
 1849        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1850        assert_eq!(editor.display_text(cx), "\n");
 1851        assert_eq!(
 1852            editor.selections.display_ranges(cx),
 1853            &[
 1854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1855                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1856            ]
 1857        );
 1858    });
 1859}
 1860
 1861#[gpui::test]
 1862fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1863    init_test(cx, |_| {});
 1864    let move_to_beg = MoveToBeginningOfLine {
 1865        stop_at_soft_wraps: false,
 1866        stop_at_indent: false,
 1867    };
 1868
 1869    let move_to_end = MoveToEndOfLine {
 1870        stop_at_soft_wraps: false,
 1871    };
 1872
 1873    let editor = cx.add_window(|window, cx| {
 1874        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1875        build_editor(buffer, window, cx)
 1876    });
 1877
 1878    _ = editor.update(cx, |editor, window, cx| {
 1879        editor.set_wrap_width(Some(140.0.into()), cx);
 1880
 1881        // We expect the following lines after wrapping
 1882        // ```
 1883        // thequickbrownfox
 1884        // jumpedoverthelazydo
 1885        // gs
 1886        // ```
 1887        // The final `gs` was soft-wrapped onto a new line.
 1888        assert_eq!(
 1889            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1890            editor.display_text(cx),
 1891        );
 1892
 1893        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1894        // Start the cursor at the `k` on the first line
 1895        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1896            s.select_display_ranges([
 1897                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1898            ]);
 1899        });
 1900
 1901        // Moving to the beginning of the line should put us at the beginning of the line.
 1902        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1903        assert_eq!(
 1904            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1905            editor.selections.display_ranges(cx)
 1906        );
 1907
 1908        // Moving to the end of the line should put us at the end of the line.
 1909        editor.move_to_end_of_line(&move_to_end, window, cx);
 1910        assert_eq!(
 1911            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1912            editor.selections.display_ranges(cx)
 1913        );
 1914
 1915        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1916        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1918            s.select_display_ranges([
 1919                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1920            ]);
 1921        });
 1922
 1923        // Moving to the beginning of the line should put us at the start of the second line of
 1924        // display text, i.e., the `j`.
 1925        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1926        assert_eq!(
 1927            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1928            editor.selections.display_ranges(cx)
 1929        );
 1930
 1931        // Moving to the beginning of the line again should be a no-op.
 1932        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1933        assert_eq!(
 1934            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1935            editor.selections.display_ranges(cx)
 1936        );
 1937
 1938        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1939        // next display line.
 1940        editor.move_to_end_of_line(&move_to_end, window, cx);
 1941        assert_eq!(
 1942            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1943            editor.selections.display_ranges(cx)
 1944        );
 1945
 1946        // Moving to the end of the line again should be a no-op.
 1947        editor.move_to_end_of_line(&move_to_end, window, cx);
 1948        assert_eq!(
 1949            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1950            editor.selections.display_ranges(cx)
 1951        );
 1952    });
 1953}
 1954
 1955#[gpui::test]
 1956fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1957    init_test(cx, |_| {});
 1958
 1959    let move_to_beg = MoveToBeginningOfLine {
 1960        stop_at_soft_wraps: true,
 1961        stop_at_indent: true,
 1962    };
 1963
 1964    let select_to_beg = SelectToBeginningOfLine {
 1965        stop_at_soft_wraps: true,
 1966        stop_at_indent: true,
 1967    };
 1968
 1969    let delete_to_beg = DeleteToBeginningOfLine {
 1970        stop_at_indent: true,
 1971    };
 1972
 1973    let move_to_end = MoveToEndOfLine {
 1974        stop_at_soft_wraps: false,
 1975    };
 1976
 1977    let editor = cx.add_window(|window, cx| {
 1978        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1979        build_editor(buffer, window, cx)
 1980    });
 1981
 1982    _ = editor.update(cx, |editor, window, cx| {
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1986                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1987            ]);
 1988        });
 1989
 1990        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1991        // and the second cursor at the first non-whitespace character in the line.
 1992        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1993        assert_eq!(
 1994            editor.selections.display_ranges(cx),
 1995            &[
 1996                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1997                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1998            ]
 1999        );
 2000
 2001        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2002        // and should move the second cursor to the beginning of the line.
 2003        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2004        assert_eq!(
 2005            editor.selections.display_ranges(cx),
 2006            &[
 2007                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2008                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2009            ]
 2010        );
 2011
 2012        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2013        // and should move the second cursor back to the first non-whitespace character in the line.
 2014        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2015        assert_eq!(
 2016            editor.selections.display_ranges(cx),
 2017            &[
 2018                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2019                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2020            ]
 2021        );
 2022
 2023        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2024        // and to the first non-whitespace character in the line for the second cursor.
 2025        editor.move_to_end_of_line(&move_to_end, window, cx);
 2026        editor.move_left(&MoveLeft, window, cx);
 2027        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2028        assert_eq!(
 2029            editor.selections.display_ranges(cx),
 2030            &[
 2031                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2032                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2033            ]
 2034        );
 2035
 2036        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2037        // and should select to the beginning of the line for the second cursor.
 2038        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2039        assert_eq!(
 2040            editor.selections.display_ranges(cx),
 2041            &[
 2042                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2043                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2044            ]
 2045        );
 2046
 2047        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2048        // and should delete to the first non-whitespace character in the line for the second cursor.
 2049        editor.move_to_end_of_line(&move_to_end, window, cx);
 2050        editor.move_left(&MoveLeft, window, cx);
 2051        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2052        assert_eq!(editor.text(cx), "c\n  f");
 2053    });
 2054}
 2055
 2056#[gpui::test]
 2057fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2058    init_test(cx, |_| {});
 2059
 2060    let move_to_beg = MoveToBeginningOfLine {
 2061        stop_at_soft_wraps: true,
 2062        stop_at_indent: true,
 2063    };
 2064
 2065    let editor = cx.add_window(|window, cx| {
 2066        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2067        build_editor(buffer, window, cx)
 2068    });
 2069
 2070    _ = editor.update(cx, |editor, window, cx| {
 2071        // test cursor between line_start and indent_start
 2072        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2073            s.select_display_ranges([
 2074                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2075            ]);
 2076        });
 2077
 2078        // cursor should move to line_start
 2079        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2080        assert_eq!(
 2081            editor.selections.display_ranges(cx),
 2082            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2083        );
 2084
 2085        // cursor should move to indent_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            editor.selections.display_ranges(cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2090        );
 2091
 2092        // cursor should move to back to line_start
 2093        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2094        assert_eq!(
 2095            editor.selections.display_ranges(cx),
 2096            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2097        );
 2098    });
 2099}
 2100
 2101#[gpui::test]
 2102fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2103    init_test(cx, |_| {});
 2104
 2105    let editor = cx.add_window(|window, cx| {
 2106        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2107        build_editor(buffer, window, cx)
 2108    });
 2109    _ = editor.update(cx, |editor, window, cx| {
 2110        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2111            s.select_display_ranges([
 2112                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2113                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2114            ])
 2115        });
 2116        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2117        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2118
 2119        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2120        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2121
 2122        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2123        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2124
 2125        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2126        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2127
 2128        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2129        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2130
 2131        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2132        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2133
 2134        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2135        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2136
 2137        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2138        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2139
 2140        editor.move_right(&MoveRight, window, cx);
 2141        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2142        assert_selection_ranges(
 2143            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2144            editor,
 2145            cx,
 2146        );
 2147
 2148        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2149        assert_selection_ranges(
 2150            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154
 2155        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2156        assert_selection_ranges(
 2157            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2158            editor,
 2159            cx,
 2160        );
 2161    });
 2162}
 2163
 2164#[gpui::test]
 2165fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2166    init_test(cx, |_| {});
 2167
 2168    let editor = cx.add_window(|window, cx| {
 2169        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2170        build_editor(buffer, window, cx)
 2171    });
 2172
 2173    _ = editor.update(cx, |editor, window, cx| {
 2174        editor.set_wrap_width(Some(140.0.into()), cx);
 2175        assert_eq!(
 2176            editor.display_text(cx),
 2177            "use one::{\n    two::three::\n    four::five\n};"
 2178        );
 2179
 2180        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2181            s.select_display_ranges([
 2182                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2183            ]);
 2184        });
 2185
 2186        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2187        assert_eq!(
 2188            editor.selections.display_ranges(cx),
 2189            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2190        );
 2191
 2192        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2193        assert_eq!(
 2194            editor.selections.display_ranges(cx),
 2195            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2196        );
 2197
 2198        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2199        assert_eq!(
 2200            editor.selections.display_ranges(cx),
 2201            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2202        );
 2203
 2204        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2205        assert_eq!(
 2206            editor.selections.display_ranges(cx),
 2207            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2208        );
 2209
 2210        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2211        assert_eq!(
 2212            editor.selections.display_ranges(cx),
 2213            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2214        );
 2215
 2216        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2217        assert_eq!(
 2218            editor.selections.display_ranges(cx),
 2219            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2220        );
 2221    });
 2222}
 2223
 2224#[gpui::test]
 2225async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2226    init_test(cx, |_| {});
 2227    let mut cx = EditorTestContext::new(cx).await;
 2228
 2229    let line_height = cx.editor(|editor, window, _| {
 2230        editor
 2231            .style()
 2232            .unwrap()
 2233            .text
 2234            .line_height_in_pixels(window.rem_size())
 2235    });
 2236    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2237
 2238    cx.set_state(
 2239        &r#"ˇone
 2240        two
 2241
 2242        three
 2243        fourˇ
 2244        five
 2245
 2246        six"#
 2247            .unindent(),
 2248    );
 2249
 2250    cx.update_editor(|editor, window, cx| {
 2251        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2252    });
 2253    cx.assert_editor_state(
 2254        &r#"one
 2255        two
 2256        ˇ
 2257        three
 2258        four
 2259        five
 2260        ˇ
 2261        six"#
 2262            .unindent(),
 2263    );
 2264
 2265    cx.update_editor(|editor, window, cx| {
 2266        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2267    });
 2268    cx.assert_editor_state(
 2269        &r#"one
 2270        two
 2271
 2272        three
 2273        four
 2274        five
 2275        ˇ
 2276        sixˇ"#
 2277            .unindent(),
 2278    );
 2279
 2280    cx.update_editor(|editor, window, cx| {
 2281        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2282    });
 2283    cx.assert_editor_state(
 2284        &r#"one
 2285        two
 2286
 2287        three
 2288        four
 2289        five
 2290
 2291        sixˇ"#
 2292            .unindent(),
 2293    );
 2294
 2295    cx.update_editor(|editor, window, cx| {
 2296        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2297    });
 2298    cx.assert_editor_state(
 2299        &r#"one
 2300        two
 2301
 2302        three
 2303        four
 2304        five
 2305        ˇ
 2306        six"#
 2307            .unindent(),
 2308    );
 2309
 2310    cx.update_editor(|editor, window, cx| {
 2311        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2312    });
 2313    cx.assert_editor_state(
 2314        &r#"one
 2315        two
 2316        ˇ
 2317        three
 2318        four
 2319        five
 2320
 2321        six"#
 2322            .unindent(),
 2323    );
 2324
 2325    cx.update_editor(|editor, window, cx| {
 2326        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2327    });
 2328    cx.assert_editor_state(
 2329        &r#"ˇone
 2330        two
 2331
 2332        three
 2333        four
 2334        five
 2335
 2336        six"#
 2337            .unindent(),
 2338    );
 2339}
 2340
 2341#[gpui::test]
 2342async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2343    init_test(cx, |_| {});
 2344    let mut cx = EditorTestContext::new(cx).await;
 2345    let line_height = cx.editor(|editor, window, _| {
 2346        editor
 2347            .style()
 2348            .unwrap()
 2349            .text
 2350            .line_height_in_pixels(window.rem_size())
 2351    });
 2352    let window = cx.window;
 2353    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2354
 2355    cx.set_state(
 2356        r#"ˇone
 2357        two
 2358        three
 2359        four
 2360        five
 2361        six
 2362        seven
 2363        eight
 2364        nine
 2365        ten
 2366        "#,
 2367    );
 2368
 2369    cx.update_editor(|editor, window, cx| {
 2370        assert_eq!(
 2371            editor.snapshot(window, cx).scroll_position(),
 2372            gpui::Point::new(0., 0.)
 2373        );
 2374        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 3.)
 2378        );
 2379        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2380        assert_eq!(
 2381            editor.snapshot(window, cx).scroll_position(),
 2382            gpui::Point::new(0., 6.)
 2383        );
 2384        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2385        assert_eq!(
 2386            editor.snapshot(window, cx).scroll_position(),
 2387            gpui::Point::new(0., 3.)
 2388        );
 2389
 2390        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2391        assert_eq!(
 2392            editor.snapshot(window, cx).scroll_position(),
 2393            gpui::Point::new(0., 1.)
 2394        );
 2395        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2396        assert_eq!(
 2397            editor.snapshot(window, cx).scroll_position(),
 2398            gpui::Point::new(0., 3.)
 2399        );
 2400    });
 2401}
 2402
 2403#[gpui::test]
 2404async fn test_autoscroll(cx: &mut TestAppContext) {
 2405    init_test(cx, |_| {});
 2406    let mut cx = EditorTestContext::new(cx).await;
 2407
 2408    let line_height = cx.update_editor(|editor, window, cx| {
 2409        editor.set_vertical_scroll_margin(2, cx);
 2410        editor
 2411            .style()
 2412            .unwrap()
 2413            .text
 2414            .line_height_in_pixels(window.rem_size())
 2415    });
 2416    let window = cx.window;
 2417    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2418
 2419    cx.set_state(
 2420        r#"ˇone
 2421            two
 2422            three
 2423            four
 2424            five
 2425            six
 2426            seven
 2427            eight
 2428            nine
 2429            ten
 2430        "#,
 2431    );
 2432    cx.update_editor(|editor, window, cx| {
 2433        assert_eq!(
 2434            editor.snapshot(window, cx).scroll_position(),
 2435            gpui::Point::new(0., 0.0)
 2436        );
 2437    });
 2438
 2439    // Add a cursor below the visible area. Since both cursors cannot fit
 2440    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2441    // allows the vertical scroll margin below that cursor.
 2442    cx.update_editor(|editor, window, cx| {
 2443        editor.change_selections(Default::default(), window, cx, |selections| {
 2444            selections.select_ranges([
 2445                Point::new(0, 0)..Point::new(0, 0),
 2446                Point::new(6, 0)..Point::new(6, 0),
 2447            ]);
 2448        })
 2449    });
 2450    cx.update_editor(|editor, window, cx| {
 2451        assert_eq!(
 2452            editor.snapshot(window, cx).scroll_position(),
 2453            gpui::Point::new(0., 3.0)
 2454        );
 2455    });
 2456
 2457    // Move down. The editor cursor scrolls down to track the newest cursor.
 2458    cx.update_editor(|editor, window, cx| {
 2459        editor.move_down(&Default::default(), window, cx);
 2460    });
 2461    cx.update_editor(|editor, window, cx| {
 2462        assert_eq!(
 2463            editor.snapshot(window, cx).scroll_position(),
 2464            gpui::Point::new(0., 4.0)
 2465        );
 2466    });
 2467
 2468    // Add a cursor above the visible area. Since both cursors fit on screen,
 2469    // the editor scrolls to show both.
 2470    cx.update_editor(|editor, window, cx| {
 2471        editor.change_selections(Default::default(), window, cx, |selections| {
 2472            selections.select_ranges([
 2473                Point::new(1, 0)..Point::new(1, 0),
 2474                Point::new(6, 0)..Point::new(6, 0),
 2475            ]);
 2476        })
 2477    });
 2478    cx.update_editor(|editor, window, cx| {
 2479        assert_eq!(
 2480            editor.snapshot(window, cx).scroll_position(),
 2481            gpui::Point::new(0., 1.0)
 2482        );
 2483    });
 2484}
 2485
 2486#[gpui::test]
 2487async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2488    init_test(cx, |_| {});
 2489    let mut cx = EditorTestContext::new(cx).await;
 2490
 2491    let line_height = cx.editor(|editor, window, _cx| {
 2492        editor
 2493            .style()
 2494            .unwrap()
 2495            .text
 2496            .line_height_in_pixels(window.rem_size())
 2497    });
 2498    let window = cx.window;
 2499    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2500    cx.set_state(
 2501        &r#"
 2502        ˇone
 2503        two
 2504        threeˇ
 2505        four
 2506        five
 2507        six
 2508        seven
 2509        eight
 2510        nine
 2511        ten
 2512        "#
 2513        .unindent(),
 2514    );
 2515
 2516    cx.update_editor(|editor, window, cx| {
 2517        editor.move_page_down(&MovePageDown::default(), window, cx)
 2518    });
 2519    cx.assert_editor_state(
 2520        &r#"
 2521        one
 2522        two
 2523        three
 2524        ˇfour
 2525        five
 2526        sixˇ
 2527        seven
 2528        eight
 2529        nine
 2530        ten
 2531        "#
 2532        .unindent(),
 2533    );
 2534
 2535    cx.update_editor(|editor, window, cx| {
 2536        editor.move_page_down(&MovePageDown::default(), window, cx)
 2537    });
 2538    cx.assert_editor_state(
 2539        &r#"
 2540        one
 2541        two
 2542        three
 2543        four
 2544        five
 2545        six
 2546        ˇseven
 2547        eight
 2548        nineˇ
 2549        ten
 2550        "#
 2551        .unindent(),
 2552    );
 2553
 2554    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2555    cx.assert_editor_state(
 2556        &r#"
 2557        one
 2558        two
 2559        three
 2560        ˇfour
 2561        five
 2562        sixˇ
 2563        seven
 2564        eight
 2565        nine
 2566        ten
 2567        "#
 2568        .unindent(),
 2569    );
 2570
 2571    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2572    cx.assert_editor_state(
 2573        &r#"
 2574        ˇone
 2575        two
 2576        threeˇ
 2577        four
 2578        five
 2579        six
 2580        seven
 2581        eight
 2582        nine
 2583        ten
 2584        "#
 2585        .unindent(),
 2586    );
 2587
 2588    // Test select collapsing
 2589    cx.update_editor(|editor, window, cx| {
 2590        editor.move_page_down(&MovePageDown::default(), window, cx);
 2591        editor.move_page_down(&MovePageDown::default(), window, cx);
 2592        editor.move_page_down(&MovePageDown::default(), window, cx);
 2593    });
 2594    cx.assert_editor_state(
 2595        &r#"
 2596        one
 2597        two
 2598        three
 2599        four
 2600        five
 2601        six
 2602        seven
 2603        eight
 2604        nine
 2605        ˇten
 2606        ˇ"#
 2607        .unindent(),
 2608    );
 2609}
 2610
 2611#[gpui::test]
 2612async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2613    init_test(cx, |_| {});
 2614    let mut cx = EditorTestContext::new(cx).await;
 2615    cx.set_state("one «two threeˇ» four");
 2616    cx.update_editor(|editor, window, cx| {
 2617        editor.delete_to_beginning_of_line(
 2618            &DeleteToBeginningOfLine {
 2619                stop_at_indent: false,
 2620            },
 2621            window,
 2622            cx,
 2623        );
 2624        assert_eq!(editor.text(cx), " four");
 2625    });
 2626}
 2627
 2628#[gpui::test]
 2629async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2630    init_test(cx, |_| {});
 2631
 2632    let mut cx = EditorTestContext::new(cx).await;
 2633
 2634    // For an empty selection, the preceding word fragment is deleted.
 2635    // For non-empty selections, only selected characters are deleted.
 2636    cx.set_state("onˇe two t«hreˇ»e four");
 2637    cx.update_editor(|editor, window, cx| {
 2638        editor.delete_to_previous_word_start(
 2639            &DeleteToPreviousWordStart {
 2640                ignore_newlines: false,
 2641                ignore_brackets: false,
 2642            },
 2643            window,
 2644            cx,
 2645        );
 2646    });
 2647    cx.assert_editor_state("ˇe two tˇe four");
 2648
 2649    cx.set_state("e tˇwo te «fˇ»our");
 2650    cx.update_editor(|editor, window, cx| {
 2651        editor.delete_to_next_word_end(
 2652            &DeleteToNextWordEnd {
 2653                ignore_newlines: false,
 2654                ignore_brackets: false,
 2655            },
 2656            window,
 2657            cx,
 2658        );
 2659    });
 2660    cx.assert_editor_state("e tˇ te ˇour");
 2661}
 2662
 2663#[gpui::test]
 2664async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2665    init_test(cx, |_| {});
 2666
 2667    let mut cx = EditorTestContext::new(cx).await;
 2668
 2669    cx.set_state("here is some text    ˇwith a space");
 2670    cx.update_editor(|editor, window, cx| {
 2671        editor.delete_to_previous_word_start(
 2672            &DeleteToPreviousWordStart {
 2673                ignore_newlines: false,
 2674                ignore_brackets: true,
 2675            },
 2676            window,
 2677            cx,
 2678        );
 2679    });
 2680    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2681    cx.assert_editor_state("here is some textˇwith a space");
 2682
 2683    cx.set_state("here is some text    ˇwith a space");
 2684    cx.update_editor(|editor, window, cx| {
 2685        editor.delete_to_previous_word_start(
 2686            &DeleteToPreviousWordStart {
 2687                ignore_newlines: false,
 2688                ignore_brackets: false,
 2689            },
 2690            window,
 2691            cx,
 2692        );
 2693    });
 2694    cx.assert_editor_state("here is some textˇwith a space");
 2695
 2696    cx.set_state("here is some textˇ    with a space");
 2697    cx.update_editor(|editor, window, cx| {
 2698        editor.delete_to_next_word_end(
 2699            &DeleteToNextWordEnd {
 2700                ignore_newlines: false,
 2701                ignore_brackets: true,
 2702            },
 2703            window,
 2704            cx,
 2705        );
 2706    });
 2707    // Same happens in the other direction.
 2708    cx.assert_editor_state("here is some textˇwith a space");
 2709
 2710    cx.set_state("here is some textˇ    with a space");
 2711    cx.update_editor(|editor, window, cx| {
 2712        editor.delete_to_next_word_end(
 2713            &DeleteToNextWordEnd {
 2714                ignore_newlines: false,
 2715                ignore_brackets: false,
 2716            },
 2717            window,
 2718            cx,
 2719        );
 2720    });
 2721    cx.assert_editor_state("here is some textˇwith a space");
 2722
 2723    cx.set_state("here is some textˇ    with a space");
 2724    cx.update_editor(|editor, window, cx| {
 2725        editor.delete_to_next_word_end(
 2726            &DeleteToNextWordEnd {
 2727                ignore_newlines: true,
 2728                ignore_brackets: false,
 2729            },
 2730            window,
 2731            cx,
 2732        );
 2733    });
 2734    cx.assert_editor_state("here is some textˇwith a space");
 2735    cx.update_editor(|editor, window, cx| {
 2736        editor.delete_to_previous_word_start(
 2737            &DeleteToPreviousWordStart {
 2738                ignore_newlines: true,
 2739                ignore_brackets: false,
 2740            },
 2741            window,
 2742            cx,
 2743        );
 2744    });
 2745    cx.assert_editor_state("here is some ˇwith a space");
 2746    cx.update_editor(|editor, window, cx| {
 2747        editor.delete_to_previous_word_start(
 2748            &DeleteToPreviousWordStart {
 2749                ignore_newlines: true,
 2750                ignore_brackets: false,
 2751            },
 2752            window,
 2753            cx,
 2754        );
 2755    });
 2756    // Single whitespaces are removed with the word behind them.
 2757    cx.assert_editor_state("here is ˇwith a space");
 2758    cx.update_editor(|editor, window, cx| {
 2759        editor.delete_to_previous_word_start(
 2760            &DeleteToPreviousWordStart {
 2761                ignore_newlines: true,
 2762                ignore_brackets: false,
 2763            },
 2764            window,
 2765            cx,
 2766        );
 2767    });
 2768    cx.assert_editor_state("here ˇwith a space");
 2769    cx.update_editor(|editor, window, cx| {
 2770        editor.delete_to_previous_word_start(
 2771            &DeleteToPreviousWordStart {
 2772                ignore_newlines: true,
 2773                ignore_brackets: false,
 2774            },
 2775            window,
 2776            cx,
 2777        );
 2778    });
 2779    cx.assert_editor_state("ˇwith a space");
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_previous_word_start(
 2782            &DeleteToPreviousWordStart {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    cx.assert_editor_state("ˇwith a space");
 2791    cx.update_editor(|editor, window, cx| {
 2792        editor.delete_to_next_word_end(
 2793            &DeleteToNextWordEnd {
 2794                ignore_newlines: true,
 2795                ignore_brackets: false,
 2796            },
 2797            window,
 2798            cx,
 2799        );
 2800    });
 2801    // Same happens in the other direction.
 2802    cx.assert_editor_state("ˇ a space");
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    cx.assert_editor_state("ˇ space");
 2814    cx.update_editor(|editor, window, cx| {
 2815        editor.delete_to_next_word_end(
 2816            &DeleteToNextWordEnd {
 2817                ignore_newlines: true,
 2818                ignore_brackets: false,
 2819            },
 2820            window,
 2821            cx,
 2822        );
 2823    });
 2824    cx.assert_editor_state("ˇ");
 2825    cx.update_editor(|editor, window, cx| {
 2826        editor.delete_to_next_word_end(
 2827            &DeleteToNextWordEnd {
 2828                ignore_newlines: true,
 2829                ignore_brackets: false,
 2830            },
 2831            window,
 2832            cx,
 2833        );
 2834    });
 2835    cx.assert_editor_state("ˇ");
 2836    cx.update_editor(|editor, window, cx| {
 2837        editor.delete_to_previous_word_start(
 2838            &DeleteToPreviousWordStart {
 2839                ignore_newlines: true,
 2840                ignore_brackets: false,
 2841            },
 2842            window,
 2843            cx,
 2844        );
 2845    });
 2846    cx.assert_editor_state("ˇ");
 2847}
 2848
 2849#[gpui::test]
 2850async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2851    init_test(cx, |_| {});
 2852
 2853    let language = Arc::new(
 2854        Language::new(
 2855            LanguageConfig {
 2856                brackets: BracketPairConfig {
 2857                    pairs: vec![
 2858                        BracketPair {
 2859                            start: "\"".to_string(),
 2860                            end: "\"".to_string(),
 2861                            close: true,
 2862                            surround: true,
 2863                            newline: false,
 2864                        },
 2865                        BracketPair {
 2866                            start: "(".to_string(),
 2867                            end: ")".to_string(),
 2868                            close: true,
 2869                            surround: true,
 2870                            newline: true,
 2871                        },
 2872                    ],
 2873                    ..BracketPairConfig::default()
 2874                },
 2875                ..LanguageConfig::default()
 2876            },
 2877            Some(tree_sitter_rust::LANGUAGE.into()),
 2878        )
 2879        .with_brackets_query(
 2880            r#"
 2881                ("(" @open ")" @close)
 2882                ("\"" @open "\"" @close)
 2883            "#,
 2884        )
 2885        .unwrap(),
 2886    );
 2887
 2888    let mut cx = EditorTestContext::new(cx).await;
 2889    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2890
 2891    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2892    cx.update_editor(|editor, window, cx| {
 2893        editor.delete_to_previous_word_start(
 2894            &DeleteToPreviousWordStart {
 2895                ignore_newlines: true,
 2896                ignore_brackets: false,
 2897            },
 2898            window,
 2899            cx,
 2900        );
 2901    });
 2902    // Deletion stops before brackets if asked to not ignore them.
 2903    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2904    cx.update_editor(|editor, window, cx| {
 2905        editor.delete_to_previous_word_start(
 2906            &DeleteToPreviousWordStart {
 2907                ignore_newlines: true,
 2908                ignore_brackets: false,
 2909            },
 2910            window,
 2911            cx,
 2912        );
 2913    });
 2914    // Deletion has to remove a single bracket and then stop again.
 2915    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2916
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_previous_word_start(
 2919            &DeleteToPreviousWordStart {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2928
 2929    cx.update_editor(|editor, window, cx| {
 2930        editor.delete_to_previous_word_start(
 2931            &DeleteToPreviousWordStart {
 2932                ignore_newlines: true,
 2933                ignore_brackets: false,
 2934            },
 2935            window,
 2936            cx,
 2937        );
 2938    });
 2939    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2940
 2941    cx.update_editor(|editor, window, cx| {
 2942        editor.delete_to_previous_word_start(
 2943            &DeleteToPreviousWordStart {
 2944                ignore_newlines: true,
 2945                ignore_brackets: false,
 2946            },
 2947            window,
 2948            cx,
 2949        );
 2950    });
 2951    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2952
 2953    cx.update_editor(|editor, window, cx| {
 2954        editor.delete_to_next_word_end(
 2955            &DeleteToNextWordEnd {
 2956                ignore_newlines: true,
 2957                ignore_brackets: false,
 2958            },
 2959            window,
 2960            cx,
 2961        );
 2962    });
 2963    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2964    cx.assert_editor_state(r#"ˇ");"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_next_word_end(
 2968            &DeleteToNextWordEnd {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇ"#);
 2977
 2978    cx.update_editor(|editor, window, cx| {
 2979        editor.delete_to_next_word_end(
 2980            &DeleteToNextWordEnd {
 2981                ignore_newlines: true,
 2982                ignore_brackets: false,
 2983            },
 2984            window,
 2985            cx,
 2986        );
 2987    });
 2988    cx.assert_editor_state(r#"ˇ"#);
 2989
 2990    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2991    cx.update_editor(|editor, window, cx| {
 2992        editor.delete_to_previous_word_start(
 2993            &DeleteToPreviousWordStart {
 2994                ignore_newlines: true,
 2995                ignore_brackets: true,
 2996            },
 2997            window,
 2998            cx,
 2999        );
 3000    });
 3001    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3002}
 3003
 3004#[gpui::test]
 3005fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3006    init_test(cx, |_| {});
 3007
 3008    let editor = cx.add_window(|window, cx| {
 3009        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3010        build_editor(buffer, window, cx)
 3011    });
 3012    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3013        ignore_newlines: false,
 3014        ignore_brackets: false,
 3015    };
 3016    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3017        ignore_newlines: true,
 3018        ignore_brackets: false,
 3019    };
 3020
 3021    _ = editor.update(cx, |editor, window, cx| {
 3022        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3023            s.select_display_ranges([
 3024                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3025            ])
 3026        });
 3027        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3028        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3029        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3030        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3031        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3032        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3033        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3034        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3035        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3036        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3037        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3038        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3039    });
 3040}
 3041
 3042#[gpui::test]
 3043fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3044    init_test(cx, |_| {});
 3045
 3046    let editor = cx.add_window(|window, cx| {
 3047        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3048        build_editor(buffer, window, cx)
 3049    });
 3050    let del_to_next_word_end = DeleteToNextWordEnd {
 3051        ignore_newlines: false,
 3052        ignore_brackets: false,
 3053    };
 3054    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3055        ignore_newlines: true,
 3056        ignore_brackets: false,
 3057    };
 3058
 3059    _ = editor.update(cx, |editor, window, cx| {
 3060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3061            s.select_display_ranges([
 3062                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3063            ])
 3064        });
 3065        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3066        assert_eq!(
 3067            editor.buffer.read(cx).read(cx).text(),
 3068            "one\n   two\nthree\n   four"
 3069        );
 3070        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3071        assert_eq!(
 3072            editor.buffer.read(cx).read(cx).text(),
 3073            "\n   two\nthree\n   four"
 3074        );
 3075        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3076        assert_eq!(
 3077            editor.buffer.read(cx).read(cx).text(),
 3078            "two\nthree\n   four"
 3079        );
 3080        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3081        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3082        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3083        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3084        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3085        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3086        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3087        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3088    });
 3089}
 3090
 3091#[gpui::test]
 3092fn test_newline(cx: &mut TestAppContext) {
 3093    init_test(cx, |_| {});
 3094
 3095    let editor = cx.add_window(|window, cx| {
 3096        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3097        build_editor(buffer, window, cx)
 3098    });
 3099
 3100    _ = editor.update(cx, |editor, window, cx| {
 3101        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3102            s.select_display_ranges([
 3103                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3104                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3105                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3106            ])
 3107        });
 3108
 3109        editor.newline(&Newline, window, cx);
 3110        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3111    });
 3112}
 3113
 3114#[gpui::test]
 3115fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3116    init_test(cx, |_| {});
 3117
 3118    let editor = cx.add_window(|window, cx| {
 3119        let buffer = MultiBuffer::build_simple(
 3120            "
 3121                a
 3122                b(
 3123                    X
 3124                )
 3125                c(
 3126                    X
 3127                )
 3128            "
 3129            .unindent()
 3130            .as_str(),
 3131            cx,
 3132        );
 3133        let mut editor = build_editor(buffer, window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([
 3136                Point::new(2, 4)..Point::new(2, 5),
 3137                Point::new(5, 4)..Point::new(5, 5),
 3138            ])
 3139        });
 3140        editor
 3141    });
 3142
 3143    _ = editor.update(cx, |editor, window, cx| {
 3144        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3145        editor.buffer.update(cx, |buffer, cx| {
 3146            buffer.edit(
 3147                [
 3148                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3149                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3150                ],
 3151                None,
 3152                cx,
 3153            );
 3154            assert_eq!(
 3155                buffer.read(cx).text(),
 3156                "
 3157                    a
 3158                    b()
 3159                    c()
 3160                "
 3161                .unindent()
 3162            );
 3163        });
 3164        assert_eq!(
 3165            editor.selections.ranges(cx),
 3166            &[
 3167                Point::new(1, 2)..Point::new(1, 2),
 3168                Point::new(2, 2)..Point::new(2, 2),
 3169            ],
 3170        );
 3171
 3172        editor.newline(&Newline, window, cx);
 3173        assert_eq!(
 3174            editor.text(cx),
 3175            "
 3176                a
 3177                b(
 3178                )
 3179                c(
 3180                )
 3181            "
 3182            .unindent()
 3183        );
 3184
 3185        // The selections are moved after the inserted newlines
 3186        assert_eq!(
 3187            editor.selections.ranges(cx),
 3188            &[
 3189                Point::new(2, 0)..Point::new(2, 0),
 3190                Point::new(4, 0)..Point::new(4, 0),
 3191            ],
 3192        );
 3193    });
 3194}
 3195
 3196#[gpui::test]
 3197async fn test_newline_above(cx: &mut TestAppContext) {
 3198    init_test(cx, |settings| {
 3199        settings.defaults.tab_size = NonZeroU32::new(4)
 3200    });
 3201
 3202    let language = Arc::new(
 3203        Language::new(
 3204            LanguageConfig::default(),
 3205            Some(tree_sitter_rust::LANGUAGE.into()),
 3206        )
 3207        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3208        .unwrap(),
 3209    );
 3210
 3211    let mut cx = EditorTestContext::new(cx).await;
 3212    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3213    cx.set_state(indoc! {"
 3214        const a: ˇA = (
 3215 3216                «const_functionˇ»(ˇ),
 3217                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3218 3219        ˇ);ˇ
 3220    "});
 3221
 3222    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3223    cx.assert_editor_state(indoc! {"
 3224        ˇ
 3225        const a: A = (
 3226            ˇ
 3227            (
 3228                ˇ
 3229                ˇ
 3230                const_function(),
 3231                ˇ
 3232                ˇ
 3233                ˇ
 3234                ˇ
 3235                something_else,
 3236                ˇ
 3237            )
 3238            ˇ
 3239            ˇ
 3240        );
 3241    "});
 3242}
 3243
 3244#[gpui::test]
 3245async fn test_newline_below(cx: &mut TestAppContext) {
 3246    init_test(cx, |settings| {
 3247        settings.defaults.tab_size = NonZeroU32::new(4)
 3248    });
 3249
 3250    let language = Arc::new(
 3251        Language::new(
 3252            LanguageConfig::default(),
 3253            Some(tree_sitter_rust::LANGUAGE.into()),
 3254        )
 3255        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3256        .unwrap(),
 3257    );
 3258
 3259    let mut cx = EditorTestContext::new(cx).await;
 3260    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3261    cx.set_state(indoc! {"
 3262        const a: ˇA = (
 3263 3264                «const_functionˇ»(ˇ),
 3265                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3266 3267        ˇ);ˇ
 3268    "});
 3269
 3270    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3271    cx.assert_editor_state(indoc! {"
 3272        const a: A = (
 3273            ˇ
 3274            (
 3275                ˇ
 3276                const_function(),
 3277                ˇ
 3278                ˇ
 3279                something_else,
 3280                ˇ
 3281                ˇ
 3282                ˇ
 3283                ˇ
 3284            )
 3285            ˇ
 3286        );
 3287        ˇ
 3288        ˇ
 3289    "});
 3290}
 3291
 3292#[gpui::test]
 3293async fn test_newline_comments(cx: &mut TestAppContext) {
 3294    init_test(cx, |settings| {
 3295        settings.defaults.tab_size = NonZeroU32::new(4)
 3296    });
 3297
 3298    let language = Arc::new(Language::new(
 3299        LanguageConfig {
 3300            line_comments: vec!["// ".into()],
 3301            ..LanguageConfig::default()
 3302        },
 3303        None,
 3304    ));
 3305    {
 3306        let mut cx = EditorTestContext::new(cx).await;
 3307        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3308        cx.set_state(indoc! {"
 3309        // Fooˇ
 3310    "});
 3311
 3312        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3313        cx.assert_editor_state(indoc! {"
 3314        // Foo
 3315        // ˇ
 3316    "});
 3317        // Ensure that we add comment prefix when existing line contains space
 3318        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3319        cx.assert_editor_state(
 3320            indoc! {"
 3321        // Foo
 3322        //s
 3323        // ˇ
 3324    "}
 3325            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3326            .as_str(),
 3327        );
 3328        // Ensure that we add comment prefix when existing line does not contain space
 3329        cx.set_state(indoc! {"
 3330        // Foo
 3331        //ˇ
 3332    "});
 3333        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3334        cx.assert_editor_state(indoc! {"
 3335        // Foo
 3336        //
 3337        // ˇ
 3338    "});
 3339        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3340        cx.set_state(indoc! {"
 3341        ˇ// Foo
 3342    "});
 3343        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3344        cx.assert_editor_state(indoc! {"
 3345
 3346        ˇ// Foo
 3347    "});
 3348    }
 3349    // Ensure that comment continuations can be disabled.
 3350    update_test_language_settings(cx, |settings| {
 3351        settings.defaults.extend_comment_on_newline = Some(false);
 3352    });
 3353    let mut cx = EditorTestContext::new(cx).await;
 3354    cx.set_state(indoc! {"
 3355        // Fooˇ
 3356    "});
 3357    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3358    cx.assert_editor_state(indoc! {"
 3359        // Foo
 3360        ˇ
 3361    "});
 3362}
 3363
 3364#[gpui::test]
 3365async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3366    init_test(cx, |settings| {
 3367        settings.defaults.tab_size = NonZeroU32::new(4)
 3368    });
 3369
 3370    let language = Arc::new(Language::new(
 3371        LanguageConfig {
 3372            line_comments: vec!["// ".into(), "/// ".into()],
 3373            ..LanguageConfig::default()
 3374        },
 3375        None,
 3376    ));
 3377    {
 3378        let mut cx = EditorTestContext::new(cx).await;
 3379        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3380        cx.set_state(indoc! {"
 3381        //ˇ
 3382    "});
 3383        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3384        cx.assert_editor_state(indoc! {"
 3385        //
 3386        // ˇ
 3387    "});
 3388
 3389        cx.set_state(indoc! {"
 3390        ///ˇ
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(indoc! {"
 3394        ///
 3395        /// ˇ
 3396    "});
 3397    }
 3398}
 3399
 3400#[gpui::test]
 3401async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3402    init_test(cx, |settings| {
 3403        settings.defaults.tab_size = NonZeroU32::new(4)
 3404    });
 3405
 3406    let language = Arc::new(
 3407        Language::new(
 3408            LanguageConfig {
 3409                documentation_comment: Some(language::BlockCommentConfig {
 3410                    start: "/**".into(),
 3411                    end: "*/".into(),
 3412                    prefix: "* ".into(),
 3413                    tab_size: 1,
 3414                }),
 3415
 3416                ..LanguageConfig::default()
 3417            },
 3418            Some(tree_sitter_rust::LANGUAGE.into()),
 3419        )
 3420        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3421        .unwrap(),
 3422    );
 3423
 3424    {
 3425        let mut cx = EditorTestContext::new(cx).await;
 3426        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3427        cx.set_state(indoc! {"
 3428        /**ˇ
 3429    "});
 3430
 3431        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3432        cx.assert_editor_state(indoc! {"
 3433        /**
 3434         * ˇ
 3435    "});
 3436        // Ensure that if cursor is before the comment start,
 3437        // we do not actually insert a comment prefix.
 3438        cx.set_state(indoc! {"
 3439        ˇ/**
 3440    "});
 3441        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3442        cx.assert_editor_state(indoc! {"
 3443
 3444        ˇ/**
 3445    "});
 3446        // Ensure that if cursor is between it doesn't add comment prefix.
 3447        cx.set_state(indoc! {"
 3448        /*ˇ*
 3449    "});
 3450        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3451        cx.assert_editor_state(indoc! {"
 3452        /*
 3453        ˇ*
 3454    "});
 3455        // Ensure that if suffix exists on same line after cursor it adds new line.
 3456        cx.set_state(indoc! {"
 3457        /**ˇ*/
 3458    "});
 3459        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3460        cx.assert_editor_state(indoc! {"
 3461        /**
 3462         * ˇ
 3463         */
 3464    "});
 3465        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3466        cx.set_state(indoc! {"
 3467        /**ˇ */
 3468    "});
 3469        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3470        cx.assert_editor_state(indoc! {"
 3471        /**
 3472         * ˇ
 3473         */
 3474    "});
 3475        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3476        cx.set_state(indoc! {"
 3477        /** ˇ*/
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(
 3481            indoc! {"
 3482        /**s
 3483         * ˇ
 3484         */
 3485    "}
 3486            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3487            .as_str(),
 3488        );
 3489        // Ensure that delimiter space is preserved when newline on already
 3490        // spaced delimiter.
 3491        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3492        cx.assert_editor_state(
 3493            indoc! {"
 3494        /**s
 3495         *s
 3496         * ˇ
 3497         */
 3498    "}
 3499            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3500            .as_str(),
 3501        );
 3502        // Ensure that delimiter space is preserved when space is not
 3503        // on existing delimiter.
 3504        cx.set_state(indoc! {"
 3505        /**
 3506 3507         */
 3508    "});
 3509        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3510        cx.assert_editor_state(indoc! {"
 3511        /**
 3512         *
 3513         * ˇ
 3514         */
 3515    "});
 3516        // Ensure that if suffix exists on same line after cursor it
 3517        // doesn't add extra new line if prefix is not on same line.
 3518        cx.set_state(indoc! {"
 3519        /**
 3520        ˇ*/
 3521    "});
 3522        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3523        cx.assert_editor_state(indoc! {"
 3524        /**
 3525
 3526        ˇ*/
 3527    "});
 3528        // Ensure that it detects suffix after existing prefix.
 3529        cx.set_state(indoc! {"
 3530        /**ˇ/
 3531    "});
 3532        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3533        cx.assert_editor_state(indoc! {"
 3534        /**
 3535        ˇ/
 3536    "});
 3537        // Ensure that if suffix exists on same line before
 3538        // cursor it does not add comment prefix.
 3539        cx.set_state(indoc! {"
 3540        /** */ˇ
 3541    "});
 3542        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3543        cx.assert_editor_state(indoc! {"
 3544        /** */
 3545        ˇ
 3546    "});
 3547        // Ensure that if suffix exists on same line before
 3548        // cursor it does not add comment prefix.
 3549        cx.set_state(indoc! {"
 3550        /**
 3551         *
 3552         */ˇ
 3553    "});
 3554        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3555        cx.assert_editor_state(indoc! {"
 3556        /**
 3557         *
 3558         */
 3559         ˇ
 3560    "});
 3561
 3562        // Ensure that inline comment followed by code
 3563        // doesn't add comment prefix on newline
 3564        cx.set_state(indoc! {"
 3565        /** */ textˇ
 3566    "});
 3567        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568        cx.assert_editor_state(indoc! {"
 3569        /** */ text
 3570        ˇ
 3571    "});
 3572
 3573        // Ensure that text after comment end tag
 3574        // doesn't add comment prefix on newline
 3575        cx.set_state(indoc! {"
 3576        /**
 3577         *
 3578         */ˇtext
 3579    "});
 3580        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3581        cx.assert_editor_state(indoc! {"
 3582        /**
 3583         *
 3584         */
 3585         ˇtext
 3586    "});
 3587
 3588        // Ensure if not comment block it doesn't
 3589        // add comment prefix on newline
 3590        cx.set_state(indoc! {"
 3591        * textˇ
 3592    "});
 3593        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3594        cx.assert_editor_state(indoc! {"
 3595        * text
 3596        ˇ
 3597    "});
 3598    }
 3599    // Ensure that comment continuations can be disabled.
 3600    update_test_language_settings(cx, |settings| {
 3601        settings.defaults.extend_comment_on_newline = Some(false);
 3602    });
 3603    let mut cx = EditorTestContext::new(cx).await;
 3604    cx.set_state(indoc! {"
 3605        /**ˇ
 3606    "});
 3607    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3608    cx.assert_editor_state(indoc! {"
 3609        /**
 3610        ˇ
 3611    "});
 3612}
 3613
 3614#[gpui::test]
 3615async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3616    init_test(cx, |settings| {
 3617        settings.defaults.tab_size = NonZeroU32::new(4)
 3618    });
 3619
 3620    let lua_language = Arc::new(Language::new(
 3621        LanguageConfig {
 3622            line_comments: vec!["--".into()],
 3623            block_comment: Some(language::BlockCommentConfig {
 3624                start: "--[[".into(),
 3625                prefix: "".into(),
 3626                end: "]]".into(),
 3627                tab_size: 0,
 3628            }),
 3629            ..LanguageConfig::default()
 3630        },
 3631        None,
 3632    ));
 3633
 3634    let mut cx = EditorTestContext::new(cx).await;
 3635    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3636
 3637    // Line with line comment should extend
 3638    cx.set_state(indoc! {"
 3639        --ˇ
 3640    "});
 3641    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3642    cx.assert_editor_state(indoc! {"
 3643        --
 3644        --ˇ
 3645    "});
 3646
 3647    // Line with block comment that matches line comment should not extend
 3648    cx.set_state(indoc! {"
 3649        --[[ˇ
 3650    "});
 3651    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3652    cx.assert_editor_state(indoc! {"
 3653        --[[
 3654        ˇ
 3655    "});
 3656}
 3657
 3658#[gpui::test]
 3659fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3660    init_test(cx, |_| {});
 3661
 3662    let editor = cx.add_window(|window, cx| {
 3663        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3664        let mut editor = build_editor(buffer, window, cx);
 3665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3666            s.select_ranges([3..4, 11..12, 19..20])
 3667        });
 3668        editor
 3669    });
 3670
 3671    _ = editor.update(cx, |editor, window, cx| {
 3672        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3673        editor.buffer.update(cx, |buffer, cx| {
 3674            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3675            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3676        });
 3677        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3678
 3679        editor.insert("Z", window, cx);
 3680        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3681
 3682        // The selections are moved after the inserted characters
 3683        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3684    });
 3685}
 3686
 3687#[gpui::test]
 3688async fn test_tab(cx: &mut TestAppContext) {
 3689    init_test(cx, |settings| {
 3690        settings.defaults.tab_size = NonZeroU32::new(3)
 3691    });
 3692
 3693    let mut cx = EditorTestContext::new(cx).await;
 3694    cx.set_state(indoc! {"
 3695        ˇabˇc
 3696        ˇ🏀ˇ🏀ˇefg
 3697 3698    "});
 3699    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3700    cx.assert_editor_state(indoc! {"
 3701           ˇab ˇc
 3702           ˇ🏀  ˇ🏀  ˇefg
 3703        d  ˇ
 3704    "});
 3705
 3706    cx.set_state(indoc! {"
 3707        a
 3708        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3709    "});
 3710    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3711    cx.assert_editor_state(indoc! {"
 3712        a
 3713           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3714    "});
 3715}
 3716
 3717#[gpui::test]
 3718async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3719    init_test(cx, |_| {});
 3720
 3721    let mut cx = EditorTestContext::new(cx).await;
 3722    let language = Arc::new(
 3723        Language::new(
 3724            LanguageConfig::default(),
 3725            Some(tree_sitter_rust::LANGUAGE.into()),
 3726        )
 3727        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3728        .unwrap(),
 3729    );
 3730    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3731
 3732    // test when all cursors are not at suggested indent
 3733    // then simply move to their suggested indent location
 3734    cx.set_state(indoc! {"
 3735        const a: B = (
 3736            c(
 3737        ˇ
 3738        ˇ    )
 3739        );
 3740    "});
 3741    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3742    cx.assert_editor_state(indoc! {"
 3743        const a: B = (
 3744            c(
 3745                ˇ
 3746            ˇ)
 3747        );
 3748    "});
 3749
 3750    // test cursor already at suggested indent not moving when
 3751    // other cursors are yet to reach their suggested indents
 3752    cx.set_state(indoc! {"
 3753        ˇ
 3754        const a: B = (
 3755            c(
 3756                d(
 3757        ˇ
 3758                )
 3759        ˇ
 3760        ˇ    )
 3761        );
 3762    "});
 3763    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3764    cx.assert_editor_state(indoc! {"
 3765        ˇ
 3766        const a: B = (
 3767            c(
 3768                d(
 3769                    ˇ
 3770                )
 3771                ˇ
 3772            ˇ)
 3773        );
 3774    "});
 3775    // test when all cursors are at suggested indent then tab is inserted
 3776    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3777    cx.assert_editor_state(indoc! {"
 3778            ˇ
 3779        const a: B = (
 3780            c(
 3781                d(
 3782                        ˇ
 3783                )
 3784                    ˇ
 3785                ˇ)
 3786        );
 3787    "});
 3788
 3789    // test when current indent is less than suggested indent,
 3790    // we adjust line to match suggested indent and move cursor to it
 3791    //
 3792    // when no other cursor is at word boundary, all of them should move
 3793    cx.set_state(indoc! {"
 3794        const a: B = (
 3795            c(
 3796                d(
 3797        ˇ
 3798        ˇ   )
 3799        ˇ   )
 3800        );
 3801    "});
 3802    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3803    cx.assert_editor_state(indoc! {"
 3804        const a: B = (
 3805            c(
 3806                d(
 3807                    ˇ
 3808                ˇ)
 3809            ˇ)
 3810        );
 3811    "});
 3812
 3813    // test when current indent is less than suggested indent,
 3814    // we adjust line to match suggested indent and move cursor to it
 3815    //
 3816    // when some other cursor is at word boundary, it should not move
 3817    cx.set_state(indoc! {"
 3818        const a: B = (
 3819            c(
 3820                d(
 3821        ˇ
 3822        ˇ   )
 3823           ˇ)
 3824        );
 3825    "});
 3826    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3827    cx.assert_editor_state(indoc! {"
 3828        const a: B = (
 3829            c(
 3830                d(
 3831                    ˇ
 3832                ˇ)
 3833            ˇ)
 3834        );
 3835    "});
 3836
 3837    // test when current indent is more than suggested indent,
 3838    // we just move cursor to current indent instead of suggested indent
 3839    //
 3840    // when no other cursor is at word boundary, all of them should move
 3841    cx.set_state(indoc! {"
 3842        const a: B = (
 3843            c(
 3844                d(
 3845        ˇ
 3846        ˇ                )
 3847        ˇ   )
 3848        );
 3849    "});
 3850    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3851    cx.assert_editor_state(indoc! {"
 3852        const a: B = (
 3853            c(
 3854                d(
 3855                    ˇ
 3856                        ˇ)
 3857            ˇ)
 3858        );
 3859    "});
 3860    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3861    cx.assert_editor_state(indoc! {"
 3862        const a: B = (
 3863            c(
 3864                d(
 3865                        ˇ
 3866                            ˇ)
 3867                ˇ)
 3868        );
 3869    "});
 3870
 3871    // test when current indent is more than suggested indent,
 3872    // we just move cursor to current indent instead of suggested indent
 3873    //
 3874    // when some other cursor is at word boundary, it doesn't move
 3875    cx.set_state(indoc! {"
 3876        const a: B = (
 3877            c(
 3878                d(
 3879        ˇ
 3880        ˇ                )
 3881            ˇ)
 3882        );
 3883    "});
 3884    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3885    cx.assert_editor_state(indoc! {"
 3886        const a: B = (
 3887            c(
 3888                d(
 3889                    ˇ
 3890                        ˇ)
 3891            ˇ)
 3892        );
 3893    "});
 3894
 3895    // handle auto-indent when there are multiple cursors on the same line
 3896    cx.set_state(indoc! {"
 3897        const a: B = (
 3898            c(
 3899        ˇ    ˇ
 3900        ˇ    )
 3901        );
 3902    "});
 3903    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3904    cx.assert_editor_state(indoc! {"
 3905        const a: B = (
 3906            c(
 3907                ˇ
 3908            ˇ)
 3909        );
 3910    "});
 3911}
 3912
 3913#[gpui::test]
 3914async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3915    init_test(cx, |settings| {
 3916        settings.defaults.tab_size = NonZeroU32::new(3)
 3917    });
 3918
 3919    let mut cx = EditorTestContext::new(cx).await;
 3920    cx.set_state(indoc! {"
 3921         ˇ
 3922        \t ˇ
 3923        \t  ˇ
 3924        \t   ˇ
 3925         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3926    "});
 3927
 3928    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3929    cx.assert_editor_state(indoc! {"
 3930           ˇ
 3931        \t   ˇ
 3932        \t   ˇ
 3933        \t      ˇ
 3934         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3935    "});
 3936}
 3937
 3938#[gpui::test]
 3939async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3940    init_test(cx, |settings| {
 3941        settings.defaults.tab_size = NonZeroU32::new(4)
 3942    });
 3943
 3944    let language = Arc::new(
 3945        Language::new(
 3946            LanguageConfig::default(),
 3947            Some(tree_sitter_rust::LANGUAGE.into()),
 3948        )
 3949        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3950        .unwrap(),
 3951    );
 3952
 3953    let mut cx = EditorTestContext::new(cx).await;
 3954    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3955    cx.set_state(indoc! {"
 3956        fn a() {
 3957            if b {
 3958        \t ˇc
 3959            }
 3960        }
 3961    "});
 3962
 3963    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3964    cx.assert_editor_state(indoc! {"
 3965        fn a() {
 3966            if b {
 3967                ˇc
 3968            }
 3969        }
 3970    "});
 3971}
 3972
 3973#[gpui::test]
 3974async fn test_indent_outdent(cx: &mut TestAppContext) {
 3975    init_test(cx, |settings| {
 3976        settings.defaults.tab_size = NonZeroU32::new(4);
 3977    });
 3978
 3979    let mut cx = EditorTestContext::new(cx).await;
 3980
 3981    cx.set_state(indoc! {"
 3982          «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            «oneˇ» «twoˇ»
 3989        three
 3990         four
 3991    "});
 3992
 3993    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3994    cx.assert_editor_state(indoc! {"
 3995        «oneˇ» «twoˇ»
 3996        three
 3997         four
 3998    "});
 3999
 4000    // select across line ending
 4001    cx.set_state(indoc! {"
 4002        one two
 4003        t«hree
 4004        ˇ» four
 4005    "});
 4006    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4007    cx.assert_editor_state(indoc! {"
 4008        one two
 4009            t«hree
 4010        ˇ» four
 4011    "});
 4012
 4013    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4014    cx.assert_editor_state(indoc! {"
 4015        one two
 4016        t«hree
 4017        ˇ» four
 4018    "});
 4019
 4020    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4021    cx.set_state(indoc! {"
 4022        one two
 4023        ˇthree
 4024            four
 4025    "});
 4026    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4027    cx.assert_editor_state(indoc! {"
 4028        one two
 4029            ˇthree
 4030            four
 4031    "});
 4032
 4033    cx.set_state(indoc! {"
 4034        one two
 4035        ˇ    three
 4036            four
 4037    "});
 4038    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4039    cx.assert_editor_state(indoc! {"
 4040        one two
 4041        ˇthree
 4042            four
 4043    "});
 4044}
 4045
 4046#[gpui::test]
 4047async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4048    // This is a regression test for issue #33761
 4049    init_test(cx, |_| {});
 4050
 4051    let mut cx = EditorTestContext::new(cx).await;
 4052    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4053    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4054
 4055    cx.set_state(
 4056        r#"ˇ#     ingress:
 4057ˇ#         api:
 4058ˇ#             enabled: false
 4059ˇ#             pathType: Prefix
 4060ˇ#           console:
 4061ˇ#               enabled: false
 4062ˇ#               pathType: Prefix
 4063"#,
 4064    );
 4065
 4066    // Press tab to indent all lines
 4067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4068
 4069    cx.assert_editor_state(
 4070        r#"    ˇ#     ingress:
 4071    ˇ#         api:
 4072    ˇ#             enabled: false
 4073    ˇ#             pathType: Prefix
 4074    ˇ#           console:
 4075    ˇ#               enabled: false
 4076    ˇ#               pathType: Prefix
 4077"#,
 4078    );
 4079}
 4080
 4081#[gpui::test]
 4082async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4083    // This is a test to make sure our fix for issue #33761 didn't break anything
 4084    init_test(cx, |_| {});
 4085
 4086    let mut cx = EditorTestContext::new(cx).await;
 4087    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4088    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4089
 4090    cx.set_state(
 4091        r#"ˇingress:
 4092ˇ  api:
 4093ˇ    enabled: false
 4094ˇ    pathType: Prefix
 4095"#,
 4096    );
 4097
 4098    // Press tab to indent all lines
 4099    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4100
 4101    cx.assert_editor_state(
 4102        r#"ˇingress:
 4103    ˇapi:
 4104        ˇenabled: false
 4105        ˇpathType: Prefix
 4106"#,
 4107    );
 4108}
 4109
 4110#[gpui::test]
 4111async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4112    init_test(cx, |settings| {
 4113        settings.defaults.hard_tabs = Some(true);
 4114    });
 4115
 4116    let mut cx = EditorTestContext::new(cx).await;
 4117
 4118    // select two ranges on one line
 4119    cx.set_state(indoc! {"
 4120        «oneˇ» «twoˇ»
 4121        three
 4122        four
 4123    "});
 4124    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4125    cx.assert_editor_state(indoc! {"
 4126        \t«oneˇ» «twoˇ»
 4127        three
 4128        four
 4129    "});
 4130    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4131    cx.assert_editor_state(indoc! {"
 4132        \t\t«oneˇ» «twoˇ»
 4133        three
 4134        four
 4135    "});
 4136    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4137    cx.assert_editor_state(indoc! {"
 4138        \t«oneˇ» «twoˇ»
 4139        three
 4140        four
 4141    "});
 4142    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4143    cx.assert_editor_state(indoc! {"
 4144        «oneˇ» «twoˇ»
 4145        three
 4146        four
 4147    "});
 4148
 4149    // select across a line ending
 4150    cx.set_state(indoc! {"
 4151        one two
 4152        t«hree
 4153        ˇ»four
 4154    "});
 4155    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4156    cx.assert_editor_state(indoc! {"
 4157        one two
 4158        \tt«hree
 4159        ˇ»four
 4160    "});
 4161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4162    cx.assert_editor_state(indoc! {"
 4163        one two
 4164        \t\tt«hree
 4165        ˇ»four
 4166    "});
 4167    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4168    cx.assert_editor_state(indoc! {"
 4169        one two
 4170        \tt«hree
 4171        ˇ»four
 4172    "});
 4173    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4174    cx.assert_editor_state(indoc! {"
 4175        one two
 4176        t«hree
 4177        ˇ»four
 4178    "});
 4179
 4180    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4181    cx.set_state(indoc! {"
 4182        one two
 4183        ˇthree
 4184        four
 4185    "});
 4186    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4187    cx.assert_editor_state(indoc! {"
 4188        one two
 4189        ˇthree
 4190        four
 4191    "});
 4192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4193    cx.assert_editor_state(indoc! {"
 4194        one two
 4195        \tˇthree
 4196        four
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        one two
 4201        ˇthree
 4202        four
 4203    "});
 4204}
 4205
 4206#[gpui::test]
 4207fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4208    init_test(cx, |settings| {
 4209        settings.languages.0.extend([
 4210            (
 4211                "TOML".into(),
 4212                LanguageSettingsContent {
 4213                    tab_size: NonZeroU32::new(2),
 4214                    ..Default::default()
 4215                },
 4216            ),
 4217            (
 4218                "Rust".into(),
 4219                LanguageSettingsContent {
 4220                    tab_size: NonZeroU32::new(4),
 4221                    ..Default::default()
 4222                },
 4223            ),
 4224        ]);
 4225    });
 4226
 4227    let toml_language = Arc::new(Language::new(
 4228        LanguageConfig {
 4229            name: "TOML".into(),
 4230            ..Default::default()
 4231        },
 4232        None,
 4233    ));
 4234    let rust_language = Arc::new(Language::new(
 4235        LanguageConfig {
 4236            name: "Rust".into(),
 4237            ..Default::default()
 4238        },
 4239        None,
 4240    ));
 4241
 4242    let toml_buffer =
 4243        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4244    let rust_buffer =
 4245        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4246    let multibuffer = cx.new(|cx| {
 4247        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4248        multibuffer.push_excerpts(
 4249            toml_buffer.clone(),
 4250            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4251            cx,
 4252        );
 4253        multibuffer.push_excerpts(
 4254            rust_buffer.clone(),
 4255            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4256            cx,
 4257        );
 4258        multibuffer
 4259    });
 4260
 4261    cx.add_window(|window, cx| {
 4262        let mut editor = build_editor(multibuffer, window, cx);
 4263
 4264        assert_eq!(
 4265            editor.text(cx),
 4266            indoc! {"
 4267                a = 1
 4268                b = 2
 4269
 4270                const c: usize = 3;
 4271            "}
 4272        );
 4273
 4274        select_ranges(
 4275            &mut editor,
 4276            indoc! {"
 4277                «aˇ» = 1
 4278                b = 2
 4279
 4280                «const c:ˇ» usize = 3;
 4281            "},
 4282            window,
 4283            cx,
 4284        );
 4285
 4286        editor.tab(&Tab, window, cx);
 4287        assert_text_with_selections(
 4288            &mut editor,
 4289            indoc! {"
 4290                  «aˇ» = 1
 4291                b = 2
 4292
 4293                    «const c:ˇ» usize = 3;
 4294            "},
 4295            cx,
 4296        );
 4297        editor.backtab(&Backtab, window, cx);
 4298        assert_text_with_selections(
 4299            &mut editor,
 4300            indoc! {"
 4301                «aˇ» = 1
 4302                b = 2
 4303
 4304                «const c:ˇ» usize = 3;
 4305            "},
 4306            cx,
 4307        );
 4308
 4309        editor
 4310    });
 4311}
 4312
 4313#[gpui::test]
 4314async fn test_backspace(cx: &mut TestAppContext) {
 4315    init_test(cx, |_| {});
 4316
 4317    let mut cx = EditorTestContext::new(cx).await;
 4318
 4319    // Basic backspace
 4320    cx.set_state(indoc! {"
 4321        onˇe two three
 4322        fou«rˇ» five six
 4323        seven «ˇeight nine
 4324        »ten
 4325    "});
 4326    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4327    cx.assert_editor_state(indoc! {"
 4328        oˇe two three
 4329        fouˇ five six
 4330        seven ˇten
 4331    "});
 4332
 4333    // Test backspace inside and around indents
 4334    cx.set_state(indoc! {"
 4335        zero
 4336            ˇone
 4337                ˇtwo
 4338            ˇ ˇ ˇ  three
 4339        ˇ  ˇ  four
 4340    "});
 4341    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4342    cx.assert_editor_state(indoc! {"
 4343        zero
 4344        ˇone
 4345            ˇtwo
 4346        ˇ  threeˇ  four
 4347    "});
 4348}
 4349
 4350#[gpui::test]
 4351async fn test_delete(cx: &mut TestAppContext) {
 4352    init_test(cx, |_| {});
 4353
 4354    let mut cx = EditorTestContext::new(cx).await;
 4355    cx.set_state(indoc! {"
 4356        onˇe two three
 4357        fou«rˇ» five six
 4358        seven «ˇeight nine
 4359        »ten
 4360    "});
 4361    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4362    cx.assert_editor_state(indoc! {"
 4363        onˇ two three
 4364        fouˇ five six
 4365        seven ˇten
 4366    "});
 4367}
 4368
 4369#[gpui::test]
 4370fn test_delete_line(cx: &mut TestAppContext) {
 4371    init_test(cx, |_| {});
 4372
 4373    let editor = cx.add_window(|window, cx| {
 4374        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4375        build_editor(buffer, window, cx)
 4376    });
 4377    _ = editor.update(cx, |editor, window, cx| {
 4378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4379            s.select_display_ranges([
 4380                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4381                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4382                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4383            ])
 4384        });
 4385        editor.delete_line(&DeleteLine, window, cx);
 4386        assert_eq!(editor.display_text(cx), "ghi");
 4387        assert_eq!(
 4388            editor.selections.display_ranges(cx),
 4389            vec![
 4390                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4391                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 4392            ]
 4393        );
 4394    });
 4395
 4396    let editor = cx.add_window(|window, cx| {
 4397        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4398        build_editor(buffer, window, cx)
 4399    });
 4400    _ = editor.update(cx, |editor, window, cx| {
 4401        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4402            s.select_display_ranges([
 4403                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4404            ])
 4405        });
 4406        editor.delete_line(&DeleteLine, window, cx);
 4407        assert_eq!(editor.display_text(cx), "ghi\n");
 4408        assert_eq!(
 4409            editor.selections.display_ranges(cx),
 4410            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4411        );
 4412    });
 4413}
 4414
 4415#[gpui::test]
 4416fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4417    init_test(cx, |_| {});
 4418
 4419    cx.add_window(|window, cx| {
 4420        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4421        let mut editor = build_editor(buffer.clone(), window, cx);
 4422        let buffer = buffer.read(cx).as_singleton().unwrap();
 4423
 4424        assert_eq!(
 4425            editor.selections.ranges::<Point>(cx),
 4426            &[Point::new(0, 0)..Point::new(0, 0)]
 4427        );
 4428
 4429        // When on single line, replace newline at end by space
 4430        editor.join_lines(&JoinLines, window, cx);
 4431        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4432        assert_eq!(
 4433            editor.selections.ranges::<Point>(cx),
 4434            &[Point::new(0, 3)..Point::new(0, 3)]
 4435        );
 4436
 4437        // When multiple lines are selected, remove newlines that are spanned by the selection
 4438        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4439            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4440        });
 4441        editor.join_lines(&JoinLines, window, cx);
 4442        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4443        assert_eq!(
 4444            editor.selections.ranges::<Point>(cx),
 4445            &[Point::new(0, 11)..Point::new(0, 11)]
 4446        );
 4447
 4448        // Undo should be transactional
 4449        editor.undo(&Undo, window, cx);
 4450        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4451        assert_eq!(
 4452            editor.selections.ranges::<Point>(cx),
 4453            &[Point::new(0, 5)..Point::new(2, 2)]
 4454        );
 4455
 4456        // When joining an empty line don't insert a space
 4457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4458            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4459        });
 4460        editor.join_lines(&JoinLines, window, cx);
 4461        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4462        assert_eq!(
 4463            editor.selections.ranges::<Point>(cx),
 4464            [Point::new(2, 3)..Point::new(2, 3)]
 4465        );
 4466
 4467        // We can remove trailing newlines
 4468        editor.join_lines(&JoinLines, window, cx);
 4469        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4470        assert_eq!(
 4471            editor.selections.ranges::<Point>(cx),
 4472            [Point::new(2, 3)..Point::new(2, 3)]
 4473        );
 4474
 4475        // We don't blow up on the last line
 4476        editor.join_lines(&JoinLines, window, cx);
 4477        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4478        assert_eq!(
 4479            editor.selections.ranges::<Point>(cx),
 4480            [Point::new(2, 3)..Point::new(2, 3)]
 4481        );
 4482
 4483        // reset to test indentation
 4484        editor.buffer.update(cx, |buffer, cx| {
 4485            buffer.edit(
 4486                [
 4487                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4488                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4489                ],
 4490                None,
 4491                cx,
 4492            )
 4493        });
 4494
 4495        // We remove any leading spaces
 4496        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4497        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4498            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4499        });
 4500        editor.join_lines(&JoinLines, window, cx);
 4501        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4502
 4503        // We don't insert a space for a line containing only spaces
 4504        editor.join_lines(&JoinLines, window, cx);
 4505        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4506
 4507        // We ignore any leading tabs
 4508        editor.join_lines(&JoinLines, window, cx);
 4509        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4510
 4511        editor
 4512    });
 4513}
 4514
 4515#[gpui::test]
 4516fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4517    init_test(cx, |_| {});
 4518
 4519    cx.add_window(|window, cx| {
 4520        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4521        let mut editor = build_editor(buffer.clone(), window, cx);
 4522        let buffer = buffer.read(cx).as_singleton().unwrap();
 4523
 4524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4525            s.select_ranges([
 4526                Point::new(0, 2)..Point::new(1, 1),
 4527                Point::new(1, 2)..Point::new(1, 2),
 4528                Point::new(3, 1)..Point::new(3, 2),
 4529            ])
 4530        });
 4531
 4532        editor.join_lines(&JoinLines, window, cx);
 4533        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4534
 4535        assert_eq!(
 4536            editor.selections.ranges::<Point>(cx),
 4537            [
 4538                Point::new(0, 7)..Point::new(0, 7),
 4539                Point::new(1, 3)..Point::new(1, 3)
 4540            ]
 4541        );
 4542        editor
 4543    });
 4544}
 4545
 4546#[gpui::test]
 4547async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4548    init_test(cx, |_| {});
 4549
 4550    let mut cx = EditorTestContext::new(cx).await;
 4551
 4552    let diff_base = r#"
 4553        Line 0
 4554        Line 1
 4555        Line 2
 4556        Line 3
 4557        "#
 4558    .unindent();
 4559
 4560    cx.set_state(
 4561        &r#"
 4562        ˇLine 0
 4563        Line 1
 4564        Line 2
 4565        Line 3
 4566        "#
 4567        .unindent(),
 4568    );
 4569
 4570    cx.set_head_text(&diff_base);
 4571    executor.run_until_parked();
 4572
 4573    // Join lines
 4574    cx.update_editor(|editor, window, cx| {
 4575        editor.join_lines(&JoinLines, window, cx);
 4576    });
 4577    executor.run_until_parked();
 4578
 4579    cx.assert_editor_state(
 4580        &r#"
 4581        Line 0ˇ Line 1
 4582        Line 2
 4583        Line 3
 4584        "#
 4585        .unindent(),
 4586    );
 4587    // Join again
 4588    cx.update_editor(|editor, window, cx| {
 4589        editor.join_lines(&JoinLines, window, cx);
 4590    });
 4591    executor.run_until_parked();
 4592
 4593    cx.assert_editor_state(
 4594        &r#"
 4595        Line 0 Line 1ˇ Line 2
 4596        Line 3
 4597        "#
 4598        .unindent(),
 4599    );
 4600}
 4601
 4602#[gpui::test]
 4603async fn test_custom_newlines_cause_no_false_positive_diffs(
 4604    executor: BackgroundExecutor,
 4605    cx: &mut TestAppContext,
 4606) {
 4607    init_test(cx, |_| {});
 4608    let mut cx = EditorTestContext::new(cx).await;
 4609    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4610    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4611    executor.run_until_parked();
 4612
 4613    cx.update_editor(|editor, window, cx| {
 4614        let snapshot = editor.snapshot(window, cx);
 4615        assert_eq!(
 4616            snapshot
 4617                .buffer_snapshot()
 4618                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4619                .collect::<Vec<_>>(),
 4620            Vec::new(),
 4621            "Should not have any diffs for files with custom newlines"
 4622        );
 4623    });
 4624}
 4625
 4626#[gpui::test]
 4627async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4628    init_test(cx, |_| {});
 4629
 4630    let mut cx = EditorTestContext::new(cx).await;
 4631
 4632    // Test sort_lines_case_insensitive()
 4633    cx.set_state(indoc! {"
 4634        «z
 4635        y
 4636        x
 4637        Z
 4638        Y
 4639        Xˇ»
 4640    "});
 4641    cx.update_editor(|e, window, cx| {
 4642        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4643    });
 4644    cx.assert_editor_state(indoc! {"
 4645        «x
 4646        X
 4647        y
 4648        Y
 4649        z
 4650        Zˇ»
 4651    "});
 4652
 4653    // Test sort_lines_by_length()
 4654    //
 4655    // Demonstrates:
 4656    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4657    // - sort is stable
 4658    cx.set_state(indoc! {"
 4659        «123
 4660        æ
 4661        12
 4662 4663        1
 4664        æˇ»
 4665    "});
 4666    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4667    cx.assert_editor_state(indoc! {"
 4668        «æ
 4669 4670        1
 4671        æ
 4672        12
 4673        123ˇ»
 4674    "});
 4675
 4676    // Test reverse_lines()
 4677    cx.set_state(indoc! {"
 4678        «5
 4679        4
 4680        3
 4681        2
 4682        1ˇ»
 4683    "});
 4684    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4685    cx.assert_editor_state(indoc! {"
 4686        «1
 4687        2
 4688        3
 4689        4
 4690        5ˇ»
 4691    "});
 4692
 4693    // Skip testing shuffle_line()
 4694
 4695    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4696    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4697
 4698    // Don't manipulate when cursor is on single line, but expand the selection
 4699    cx.set_state(indoc! {"
 4700        ddˇdd
 4701        ccc
 4702        bb
 4703        a
 4704    "});
 4705    cx.update_editor(|e, window, cx| {
 4706        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4707    });
 4708    cx.assert_editor_state(indoc! {"
 4709        «ddddˇ»
 4710        ccc
 4711        bb
 4712        a
 4713    "});
 4714
 4715    // Basic manipulate case
 4716    // Start selection moves to column 0
 4717    // End of selection shrinks to fit shorter line
 4718    cx.set_state(indoc! {"
 4719        dd«d
 4720        ccc
 4721        bb
 4722        aaaaaˇ»
 4723    "});
 4724    cx.update_editor(|e, window, cx| {
 4725        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4726    });
 4727    cx.assert_editor_state(indoc! {"
 4728        «aaaaa
 4729        bb
 4730        ccc
 4731        dddˇ»
 4732    "});
 4733
 4734    // Manipulate case with newlines
 4735    cx.set_state(indoc! {"
 4736        dd«d
 4737        ccc
 4738
 4739        bb
 4740        aaaaa
 4741
 4742        ˇ»
 4743    "});
 4744    cx.update_editor(|e, window, cx| {
 4745        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4746    });
 4747    cx.assert_editor_state(indoc! {"
 4748        «
 4749
 4750        aaaaa
 4751        bb
 4752        ccc
 4753        dddˇ»
 4754
 4755    "});
 4756
 4757    // Adding new line
 4758    cx.set_state(indoc! {"
 4759        aa«a
 4760        bbˇ»b
 4761    "});
 4762    cx.update_editor(|e, window, cx| {
 4763        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4764    });
 4765    cx.assert_editor_state(indoc! {"
 4766        «aaa
 4767        bbb
 4768        added_lineˇ»
 4769    "});
 4770
 4771    // Removing line
 4772    cx.set_state(indoc! {"
 4773        aa«a
 4774        bbbˇ»
 4775    "});
 4776    cx.update_editor(|e, window, cx| {
 4777        e.manipulate_immutable_lines(window, cx, |lines| {
 4778            lines.pop();
 4779        })
 4780    });
 4781    cx.assert_editor_state(indoc! {"
 4782        «aaaˇ»
 4783    "});
 4784
 4785    // Removing all lines
 4786    cx.set_state(indoc! {"
 4787        aa«a
 4788        bbbˇ»
 4789    "});
 4790    cx.update_editor(|e, window, cx| {
 4791        e.manipulate_immutable_lines(window, cx, |lines| {
 4792            lines.drain(..);
 4793        })
 4794    });
 4795    cx.assert_editor_state(indoc! {"
 4796        ˇ
 4797    "});
 4798}
 4799
 4800#[gpui::test]
 4801async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4802    init_test(cx, |_| {});
 4803
 4804    let mut cx = EditorTestContext::new(cx).await;
 4805
 4806    // Consider continuous selection as single selection
 4807    cx.set_state(indoc! {"
 4808        Aaa«aa
 4809        cˇ»c«c
 4810        bb
 4811        aaaˇ»aa
 4812    "});
 4813    cx.update_editor(|e, window, cx| {
 4814        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4815    });
 4816    cx.assert_editor_state(indoc! {"
 4817        «Aaaaa
 4818        ccc
 4819        bb
 4820        aaaaaˇ»
 4821    "});
 4822
 4823    cx.set_state(indoc! {"
 4824        Aaa«aa
 4825        cˇ»c«c
 4826        bb
 4827        aaaˇ»aa
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «Aaaaa
 4834        ccc
 4835        bbˇ»
 4836    "});
 4837
 4838    // Consider non continuous selection as distinct dedup operations
 4839    cx.set_state(indoc! {"
 4840        «aaaaa
 4841        bb
 4842        aaaaa
 4843        aaaaaˇ»
 4844
 4845        aaa«aaˇ»
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «aaaaa
 4852        bbˇ»
 4853
 4854        «aaaaaˇ»
 4855    "});
 4856}
 4857
 4858#[gpui::test]
 4859async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4860    init_test(cx, |_| {});
 4861
 4862    let mut cx = EditorTestContext::new(cx).await;
 4863
 4864    cx.set_state(indoc! {"
 4865        «Aaa
 4866        aAa
 4867        Aaaˇ»
 4868    "});
 4869    cx.update_editor(|e, window, cx| {
 4870        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4871    });
 4872    cx.assert_editor_state(indoc! {"
 4873        «Aaa
 4874        aAaˇ»
 4875    "});
 4876
 4877    cx.set_state(indoc! {"
 4878        «Aaa
 4879        aAa
 4880        aaAˇ»
 4881    "});
 4882    cx.update_editor(|e, window, cx| {
 4883        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4884    });
 4885    cx.assert_editor_state(indoc! {"
 4886        «Aaaˇ»
 4887    "});
 4888}
 4889
 4890#[gpui::test]
 4891async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4892    init_test(cx, |_| {});
 4893
 4894    let mut cx = EditorTestContext::new(cx).await;
 4895
 4896    let js_language = Arc::new(Language::new(
 4897        LanguageConfig {
 4898            name: "JavaScript".into(),
 4899            wrap_characters: Some(language::WrapCharactersConfig {
 4900                start_prefix: "<".into(),
 4901                start_suffix: ">".into(),
 4902                end_prefix: "</".into(),
 4903                end_suffix: ">".into(),
 4904            }),
 4905            ..LanguageConfig::default()
 4906        },
 4907        None,
 4908    ));
 4909
 4910    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4911
 4912    cx.set_state(indoc! {"
 4913        «testˇ»
 4914    "});
 4915    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4916    cx.assert_editor_state(indoc! {"
 4917        <«ˇ»>test</«ˇ»>
 4918    "});
 4919
 4920    cx.set_state(indoc! {"
 4921        «test
 4922         testˇ»
 4923    "});
 4924    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4925    cx.assert_editor_state(indoc! {"
 4926        <«ˇ»>test
 4927         test</«ˇ»>
 4928    "});
 4929
 4930    cx.set_state(indoc! {"
 4931        teˇst
 4932    "});
 4933    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4934    cx.assert_editor_state(indoc! {"
 4935        te<«ˇ»></«ˇ»>st
 4936    "});
 4937}
 4938
 4939#[gpui::test]
 4940async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4941    init_test(cx, |_| {});
 4942
 4943    let mut cx = EditorTestContext::new(cx).await;
 4944
 4945    let js_language = Arc::new(Language::new(
 4946        LanguageConfig {
 4947            name: "JavaScript".into(),
 4948            wrap_characters: Some(language::WrapCharactersConfig {
 4949                start_prefix: "<".into(),
 4950                start_suffix: ">".into(),
 4951                end_prefix: "</".into(),
 4952                end_suffix: ">".into(),
 4953            }),
 4954            ..LanguageConfig::default()
 4955        },
 4956        None,
 4957    ));
 4958
 4959    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4960
 4961    cx.set_state(indoc! {"
 4962        «testˇ»
 4963        «testˇ» «testˇ»
 4964        «testˇ»
 4965    "});
 4966    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4967    cx.assert_editor_state(indoc! {"
 4968        <«ˇ»>test</«ˇ»>
 4969        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4970        <«ˇ»>test</«ˇ»>
 4971    "});
 4972
 4973    cx.set_state(indoc! {"
 4974        «test
 4975         testˇ»
 4976        «test
 4977         testˇ»
 4978    "});
 4979    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4980    cx.assert_editor_state(indoc! {"
 4981        <«ˇ»>test
 4982         test</«ˇ»>
 4983        <«ˇ»>test
 4984         test</«ˇ»>
 4985    "});
 4986}
 4987
 4988#[gpui::test]
 4989async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4990    init_test(cx, |_| {});
 4991
 4992    let mut cx = EditorTestContext::new(cx).await;
 4993
 4994    let plaintext_language = Arc::new(Language::new(
 4995        LanguageConfig {
 4996            name: "Plain Text".into(),
 4997            ..LanguageConfig::default()
 4998        },
 4999        None,
 5000    ));
 5001
 5002    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5003
 5004    cx.set_state(indoc! {"
 5005        «testˇ»
 5006    "});
 5007    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5008    cx.assert_editor_state(indoc! {"
 5009      «testˇ»
 5010    "});
 5011}
 5012
 5013#[gpui::test]
 5014async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5015    init_test(cx, |_| {});
 5016
 5017    let mut cx = EditorTestContext::new(cx).await;
 5018
 5019    // Manipulate with multiple selections on a single line
 5020    cx.set_state(indoc! {"
 5021        dd«dd
 5022        cˇ»c«c
 5023        bb
 5024        aaaˇ»aa
 5025    "});
 5026    cx.update_editor(|e, window, cx| {
 5027        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5028    });
 5029    cx.assert_editor_state(indoc! {"
 5030        «aaaaa
 5031        bb
 5032        ccc
 5033        ddddˇ»
 5034    "});
 5035
 5036    // Manipulate with multiple disjoin selections
 5037    cx.set_state(indoc! {"
 5038 5039        4
 5040        3
 5041        2
 5042        1ˇ»
 5043
 5044        dd«dd
 5045        ccc
 5046        bb
 5047        aaaˇ»aa
 5048    "});
 5049    cx.update_editor(|e, window, cx| {
 5050        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5051    });
 5052    cx.assert_editor_state(indoc! {"
 5053        «1
 5054        2
 5055        3
 5056        4
 5057        5ˇ»
 5058
 5059        «aaaaa
 5060        bb
 5061        ccc
 5062        ddddˇ»
 5063    "});
 5064
 5065    // Adding lines on each selection
 5066    cx.set_state(indoc! {"
 5067 5068        1ˇ»
 5069
 5070        bb«bb
 5071        aaaˇ»aa
 5072    "});
 5073    cx.update_editor(|e, window, cx| {
 5074        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5075    });
 5076    cx.assert_editor_state(indoc! {"
 5077        «2
 5078        1
 5079        added lineˇ»
 5080
 5081        «bbbb
 5082        aaaaa
 5083        added lineˇ»
 5084    "});
 5085
 5086    // Removing lines on each selection
 5087    cx.set_state(indoc! {"
 5088 5089        1ˇ»
 5090
 5091        bb«bb
 5092        aaaˇ»aa
 5093    "});
 5094    cx.update_editor(|e, window, cx| {
 5095        e.manipulate_immutable_lines(window, cx, |lines| {
 5096            lines.pop();
 5097        })
 5098    });
 5099    cx.assert_editor_state(indoc! {"
 5100        «2ˇ»
 5101
 5102        «bbbbˇ»
 5103    "});
 5104}
 5105
 5106#[gpui::test]
 5107async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5108    init_test(cx, |settings| {
 5109        settings.defaults.tab_size = NonZeroU32::new(3)
 5110    });
 5111
 5112    let mut cx = EditorTestContext::new(cx).await;
 5113
 5114    // MULTI SELECTION
 5115    // Ln.1 "«" tests empty lines
 5116    // Ln.9 tests just leading whitespace
 5117    cx.set_state(indoc! {"
 5118        «
 5119        abc                 // No indentationˇ»
 5120        «\tabc              // 1 tabˇ»
 5121        \t\tabc «      ˇ»   // 2 tabs
 5122        \t ab«c             // Tab followed by space
 5123         \tabc              // Space followed by tab (3 spaces should be the result)
 5124        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5125           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5126        \t
 5127        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5128    "});
 5129    cx.update_editor(|e, window, cx| {
 5130        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5131    });
 5132    cx.assert_editor_state(
 5133        indoc! {"
 5134            «
 5135            abc                 // No indentation
 5136               abc              // 1 tab
 5137                  abc          // 2 tabs
 5138                abc             // Tab followed by space
 5139               abc              // Space followed by tab (3 spaces should be the result)
 5140                           abc   // Mixed indentation (tab conversion depends on the column)
 5141               abc         // Already space indented
 5142               ·
 5143               abc\tdef          // Only the leading tab is manipulatedˇ»
 5144        "}
 5145        .replace("·", "")
 5146        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5147    );
 5148
 5149    // Test on just a few lines, the others should remain unchanged
 5150    // Only lines (3, 5, 10, 11) should change
 5151    cx.set_state(
 5152        indoc! {"
 5153            ·
 5154            abc                 // No indentation
 5155            \tabcˇ               // 1 tab
 5156            \t\tabc             // 2 tabs
 5157            \t abcˇ              // Tab followed by space
 5158             \tabc              // Space followed by tab (3 spaces should be the result)
 5159            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5160               abc              // Already space indented
 5161            «\t
 5162            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5163        "}
 5164        .replace("·", "")
 5165        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5166    );
 5167    cx.update_editor(|e, window, cx| {
 5168        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5169    });
 5170    cx.assert_editor_state(
 5171        indoc! {"
 5172            ·
 5173            abc                 // No indentation
 5174            «   abc               // 1 tabˇ»
 5175            \t\tabc             // 2 tabs
 5176            «    abc              // Tab followed by spaceˇ»
 5177             \tabc              // Space followed by tab (3 spaces should be the result)
 5178            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5179               abc              // Already space indented
 5180            «   ·
 5181               abc\tdef          // Only the leading tab is manipulatedˇ»
 5182        "}
 5183        .replace("·", "")
 5184        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5185    );
 5186
 5187    // SINGLE SELECTION
 5188    // Ln.1 "«" tests empty lines
 5189    // Ln.9 tests just leading whitespace
 5190    cx.set_state(indoc! {"
 5191        «
 5192        abc                 // No indentation
 5193        \tabc               // 1 tab
 5194        \t\tabc             // 2 tabs
 5195        \t abc              // Tab followed by space
 5196         \tabc              // Space followed by tab (3 spaces should be the result)
 5197        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5198           abc              // Already space indented
 5199        \t
 5200        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5201    "});
 5202    cx.update_editor(|e, window, cx| {
 5203        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5204    });
 5205    cx.assert_editor_state(
 5206        indoc! {"
 5207            «
 5208            abc                 // No indentation
 5209               abc               // 1 tab
 5210                  abc             // 2 tabs
 5211                abc              // Tab followed by space
 5212               abc              // Space followed by tab (3 spaces should be the result)
 5213                           abc   // Mixed indentation (tab conversion depends on the column)
 5214               abc              // Already space indented
 5215               ·
 5216               abc\tdef          // Only the leading tab is manipulatedˇ»
 5217        "}
 5218        .replace("·", "")
 5219        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5220    );
 5221}
 5222
 5223#[gpui::test]
 5224async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5225    init_test(cx, |settings| {
 5226        settings.defaults.tab_size = NonZeroU32::new(3)
 5227    });
 5228
 5229    let mut cx = EditorTestContext::new(cx).await;
 5230
 5231    // MULTI SELECTION
 5232    // Ln.1 "«" tests empty lines
 5233    // Ln.11 tests just leading whitespace
 5234    cx.set_state(indoc! {"
 5235        «
 5236        abˇ»ˇc                 // No indentation
 5237         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5238          abc  «             // 2 spaces (< 3 so dont convert)
 5239           abc              // 3 spaces (convert)
 5240             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5241        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5242        «\t abc              // Tab followed by space
 5243         \tabc              // Space followed by tab (should be consumed due to tab)
 5244        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5245           \tˇ»  «\t
 5246           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5247    "});
 5248    cx.update_editor(|e, window, cx| {
 5249        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5250    });
 5251    cx.assert_editor_state(indoc! {"
 5252        «
 5253        abc                 // No indentation
 5254         abc                // 1 space (< 3 so dont convert)
 5255          abc               // 2 spaces (< 3 so dont convert)
 5256        \tabc              // 3 spaces (convert)
 5257        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5258        \t\t\tabc           // Already tab indented
 5259        \t abc              // Tab followed by space
 5260        \tabc              // Space followed by tab (should be consumed due to tab)
 5261        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5262        \t\t\t
 5263        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5264    "});
 5265
 5266    // Test on just a few lines, the other should remain unchanged
 5267    // Only lines (4, 8, 11, 12) should change
 5268    cx.set_state(
 5269        indoc! {"
 5270            ·
 5271            abc                 // No indentation
 5272             abc                // 1 space (< 3 so dont convert)
 5273              abc               // 2 spaces (< 3 so dont convert)
 5274            «   abc              // 3 spaces (convert)ˇ»
 5275                 abc            // 5 spaces (1 tab + 2 spaces)
 5276            \t\t\tabc           // Already tab indented
 5277            \t abc              // Tab followed by space
 5278             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5279               \t\t  \tabc      // Mixed indentation
 5280            \t \t  \t   \tabc   // Mixed indentation
 5281               \t  \tˇ
 5282            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5283        "}
 5284        .replace("·", "")
 5285        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5286    );
 5287    cx.update_editor(|e, window, cx| {
 5288        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5289    });
 5290    cx.assert_editor_state(
 5291        indoc! {"
 5292            ·
 5293            abc                 // No indentation
 5294             abc                // 1 space (< 3 so dont convert)
 5295              abc               // 2 spaces (< 3 so dont convert)
 5296            «\tabc              // 3 spaces (convert)ˇ»
 5297                 abc            // 5 spaces (1 tab + 2 spaces)
 5298            \t\t\tabc           // Already tab indented
 5299            \t abc              // Tab followed by space
 5300            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5301               \t\t  \tabc      // Mixed indentation
 5302            \t \t  \t   \tabc   // Mixed indentation
 5303            «\t\t\t
 5304            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5305        "}
 5306        .replace("·", "")
 5307        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5308    );
 5309
 5310    // SINGLE SELECTION
 5311    // Ln.1 "«" tests empty lines
 5312    // Ln.11 tests just leading whitespace
 5313    cx.set_state(indoc! {"
 5314        «
 5315        abc                 // No indentation
 5316         abc                // 1 space (< 3 so dont convert)
 5317          abc               // 2 spaces (< 3 so dont convert)
 5318           abc              // 3 spaces (convert)
 5319             abc            // 5 spaces (1 tab + 2 spaces)
 5320        \t\t\tabc           // Already tab indented
 5321        \t abc              // Tab followed by space
 5322         \tabc              // Space followed by tab (should be consumed due to tab)
 5323        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5324           \t  \t
 5325           abc   \t         // Only the leading spaces should be convertedˇ»
 5326    "});
 5327    cx.update_editor(|e, window, cx| {
 5328        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5329    });
 5330    cx.assert_editor_state(indoc! {"
 5331        «
 5332        abc                 // No indentation
 5333         abc                // 1 space (< 3 so dont convert)
 5334          abc               // 2 spaces (< 3 so dont convert)
 5335        \tabc              // 3 spaces (convert)
 5336        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5337        \t\t\tabc           // Already tab indented
 5338        \t abc              // Tab followed by space
 5339        \tabc              // Space followed by tab (should be consumed due to tab)
 5340        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5341        \t\t\t
 5342        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5343    "});
 5344}
 5345
 5346#[gpui::test]
 5347async fn test_toggle_case(cx: &mut TestAppContext) {
 5348    init_test(cx, |_| {});
 5349
 5350    let mut cx = EditorTestContext::new(cx).await;
 5351
 5352    // If all lower case -> upper case
 5353    cx.set_state(indoc! {"
 5354        «hello worldˇ»
 5355    "});
 5356    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5357    cx.assert_editor_state(indoc! {"
 5358        «HELLO WORLDˇ»
 5359    "});
 5360
 5361    // If all upper case -> lower case
 5362    cx.set_state(indoc! {"
 5363        «HELLO WORLDˇ»
 5364    "});
 5365    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5366    cx.assert_editor_state(indoc! {"
 5367        «hello worldˇ»
 5368    "});
 5369
 5370    // If any upper case characters are identified -> lower case
 5371    // This matches JetBrains IDEs
 5372    cx.set_state(indoc! {"
 5373        «hEllo worldˇ»
 5374    "});
 5375    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5376    cx.assert_editor_state(indoc! {"
 5377        «hello worldˇ»
 5378    "});
 5379}
 5380
 5381#[gpui::test]
 5382async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5383    init_test(cx, |_| {});
 5384
 5385    let mut cx = EditorTestContext::new(cx).await;
 5386
 5387    cx.set_state(indoc! {"
 5388        «implement-windows-supportˇ»
 5389    "});
 5390    cx.update_editor(|e, window, cx| {
 5391        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5392    });
 5393    cx.assert_editor_state(indoc! {"
 5394        «Implement windows supportˇ»
 5395    "});
 5396}
 5397
 5398#[gpui::test]
 5399async fn test_manipulate_text(cx: &mut TestAppContext) {
 5400    init_test(cx, |_| {});
 5401
 5402    let mut cx = EditorTestContext::new(cx).await;
 5403
 5404    // Test convert_to_upper_case()
 5405    cx.set_state(indoc! {"
 5406        «hello worldˇ»
 5407    "});
 5408    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5409    cx.assert_editor_state(indoc! {"
 5410        «HELLO WORLDˇ»
 5411    "});
 5412
 5413    // Test convert_to_lower_case()
 5414    cx.set_state(indoc! {"
 5415        «HELLO WORLDˇ»
 5416    "});
 5417    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5418    cx.assert_editor_state(indoc! {"
 5419        «hello worldˇ»
 5420    "});
 5421
 5422    // Test multiple line, single selection case
 5423    cx.set_state(indoc! {"
 5424        «The quick brown
 5425        fox jumps over
 5426        the lazy dogˇ»
 5427    "});
 5428    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5429    cx.assert_editor_state(indoc! {"
 5430        «The Quick Brown
 5431        Fox Jumps Over
 5432        The Lazy Dogˇ»
 5433    "});
 5434
 5435    // Test multiple line, single selection case
 5436    cx.set_state(indoc! {"
 5437        «The quick brown
 5438        fox jumps over
 5439        the lazy dogˇ»
 5440    "});
 5441    cx.update_editor(|e, window, cx| {
 5442        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5443    });
 5444    cx.assert_editor_state(indoc! {"
 5445        «TheQuickBrown
 5446        FoxJumpsOver
 5447        TheLazyDogˇ»
 5448    "});
 5449
 5450    // From here on out, test more complex cases of manipulate_text()
 5451
 5452    // Test no selection case - should affect words cursors are in
 5453    // Cursor at beginning, middle, and end of word
 5454    cx.set_state(indoc! {"
 5455        ˇhello big beauˇtiful worldˇ
 5456    "});
 5457    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5458    cx.assert_editor_state(indoc! {"
 5459        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5460    "});
 5461
 5462    // Test multiple selections on a single line and across multiple lines
 5463    cx.set_state(indoc! {"
 5464        «Theˇ» quick «brown
 5465        foxˇ» jumps «overˇ»
 5466        the «lazyˇ» dog
 5467    "});
 5468    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5469    cx.assert_editor_state(indoc! {"
 5470        «THEˇ» quick «BROWN
 5471        FOXˇ» jumps «OVERˇ»
 5472        the «LAZYˇ» dog
 5473    "});
 5474
 5475    // Test case where text length grows
 5476    cx.set_state(indoc! {"
 5477        «tschüߡ»
 5478    "});
 5479    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5480    cx.assert_editor_state(indoc! {"
 5481        «TSCHÜSSˇ»
 5482    "});
 5483
 5484    // Test to make sure we don't crash when text shrinks
 5485    cx.set_state(indoc! {"
 5486        aaa_bbbˇ
 5487    "});
 5488    cx.update_editor(|e, window, cx| {
 5489        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5490    });
 5491    cx.assert_editor_state(indoc! {"
 5492        «aaaBbbˇ»
 5493    "});
 5494
 5495    // Test to make sure we all aware of the fact that each word can grow and shrink
 5496    // Final selections should be aware of this fact
 5497    cx.set_state(indoc! {"
 5498        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5499    "});
 5500    cx.update_editor(|e, window, cx| {
 5501        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5502    });
 5503    cx.assert_editor_state(indoc! {"
 5504        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5505    "});
 5506
 5507    cx.set_state(indoc! {"
 5508        «hElLo, WoRld!ˇ»
 5509    "});
 5510    cx.update_editor(|e, window, cx| {
 5511        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5512    });
 5513    cx.assert_editor_state(indoc! {"
 5514        «HeLlO, wOrLD!ˇ»
 5515    "});
 5516
 5517    // Test selections with `line_mode() = true`.
 5518    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5519    cx.set_state(indoc! {"
 5520        «The quick brown
 5521        fox jumps over
 5522        tˇ»he lazy dog
 5523    "});
 5524    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5525    cx.assert_editor_state(indoc! {"
 5526        «THE QUICK BROWN
 5527        FOX JUMPS OVER
 5528        THE LAZY DOGˇ»
 5529    "});
 5530}
 5531
 5532#[gpui::test]
 5533fn test_duplicate_line(cx: &mut TestAppContext) {
 5534    init_test(cx, |_| {});
 5535
 5536    let editor = cx.add_window(|window, cx| {
 5537        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5538        build_editor(buffer, window, cx)
 5539    });
 5540    _ = editor.update(cx, |editor, window, cx| {
 5541        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5542            s.select_display_ranges([
 5543                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5544                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5545                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5546                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5547            ])
 5548        });
 5549        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5550        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5551        assert_eq!(
 5552            editor.selections.display_ranges(cx),
 5553            vec![
 5554                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5555                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5556                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5557                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5558            ]
 5559        );
 5560    });
 5561
 5562    let editor = cx.add_window(|window, cx| {
 5563        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5564        build_editor(buffer, window, cx)
 5565    });
 5566    _ = editor.update(cx, |editor, window, cx| {
 5567        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5568            s.select_display_ranges([
 5569                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5570                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5571            ])
 5572        });
 5573        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5574        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5575        assert_eq!(
 5576            editor.selections.display_ranges(cx),
 5577            vec![
 5578                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5579                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5580            ]
 5581        );
 5582    });
 5583
 5584    // With `move_upwards` the selections stay in place, except for
 5585    // the lines inserted above them
 5586    let editor = cx.add_window(|window, cx| {
 5587        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5588        build_editor(buffer, window, cx)
 5589    });
 5590    _ = editor.update(cx, |editor, window, cx| {
 5591        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5592            s.select_display_ranges([
 5593                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5594                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5595                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5596                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5597            ])
 5598        });
 5599        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5600        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5601        assert_eq!(
 5602            editor.selections.display_ranges(cx),
 5603            vec![
 5604                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5605                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5606                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5607                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5608            ]
 5609        );
 5610    });
 5611
 5612    let editor = cx.add_window(|window, cx| {
 5613        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5614        build_editor(buffer, window, cx)
 5615    });
 5616    _ = editor.update(cx, |editor, window, cx| {
 5617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5618            s.select_display_ranges([
 5619                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5621            ])
 5622        });
 5623        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5624        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5625        assert_eq!(
 5626            editor.selections.display_ranges(cx),
 5627            vec![
 5628                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5629                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5630            ]
 5631        );
 5632    });
 5633
 5634    let editor = cx.add_window(|window, cx| {
 5635        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5636        build_editor(buffer, window, cx)
 5637    });
 5638    _ = editor.update(cx, |editor, window, cx| {
 5639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5640            s.select_display_ranges([
 5641                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5642                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5643            ])
 5644        });
 5645        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5646        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5647        assert_eq!(
 5648            editor.selections.display_ranges(cx),
 5649            vec![
 5650                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5651                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5652            ]
 5653        );
 5654    });
 5655}
 5656
 5657#[gpui::test]
 5658fn test_move_line_up_down(cx: &mut TestAppContext) {
 5659    init_test(cx, |_| {});
 5660
 5661    let editor = cx.add_window(|window, cx| {
 5662        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5663        build_editor(buffer, window, cx)
 5664    });
 5665    _ = editor.update(cx, |editor, window, cx| {
 5666        editor.fold_creases(
 5667            vec![
 5668                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5669                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5670                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5671            ],
 5672            true,
 5673            window,
 5674            cx,
 5675        );
 5676        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5677            s.select_display_ranges([
 5678                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5679                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5680                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5681                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5682            ])
 5683        });
 5684        assert_eq!(
 5685            editor.display_text(cx),
 5686            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5687        );
 5688
 5689        editor.move_line_up(&MoveLineUp, window, cx);
 5690        assert_eq!(
 5691            editor.display_text(cx),
 5692            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5693        );
 5694        assert_eq!(
 5695            editor.selections.display_ranges(cx),
 5696            vec![
 5697                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5698                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5699                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5700                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5701            ]
 5702        );
 5703    });
 5704
 5705    _ = editor.update(cx, |editor, window, cx| {
 5706        editor.move_line_down(&MoveLineDown, window, cx);
 5707        assert_eq!(
 5708            editor.display_text(cx),
 5709            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5710        );
 5711        assert_eq!(
 5712            editor.selections.display_ranges(cx),
 5713            vec![
 5714                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5715                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5716                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5717                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5718            ]
 5719        );
 5720    });
 5721
 5722    _ = editor.update(cx, |editor, window, cx| {
 5723        editor.move_line_down(&MoveLineDown, window, cx);
 5724        assert_eq!(
 5725            editor.display_text(cx),
 5726            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5727        );
 5728        assert_eq!(
 5729            editor.selections.display_ranges(cx),
 5730            vec![
 5731                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5732                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5733                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5734                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5735            ]
 5736        );
 5737    });
 5738
 5739    _ = editor.update(cx, |editor, window, cx| {
 5740        editor.move_line_up(&MoveLineUp, window, cx);
 5741        assert_eq!(
 5742            editor.display_text(cx),
 5743            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5744        );
 5745        assert_eq!(
 5746            editor.selections.display_ranges(cx),
 5747            vec![
 5748                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5749                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5750                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5751                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5752            ]
 5753        );
 5754    });
 5755}
 5756
 5757#[gpui::test]
 5758fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5759    init_test(cx, |_| {});
 5760    let editor = cx.add_window(|window, cx| {
 5761        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5762        build_editor(buffer, window, cx)
 5763    });
 5764    _ = editor.update(cx, |editor, window, cx| {
 5765        editor.fold_creases(
 5766            vec![Crease::simple(
 5767                Point::new(6, 4)..Point::new(7, 4),
 5768                FoldPlaceholder::test(),
 5769            )],
 5770            true,
 5771            window,
 5772            cx,
 5773        );
 5774        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5775            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5776        });
 5777        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5778        editor.move_line_up(&MoveLineUp, window, cx);
 5779        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5780        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5781    });
 5782}
 5783
 5784#[gpui::test]
 5785fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5786    init_test(cx, |_| {});
 5787
 5788    let editor = cx.add_window(|window, cx| {
 5789        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5790        build_editor(buffer, window, cx)
 5791    });
 5792    _ = editor.update(cx, |editor, window, cx| {
 5793        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5794        editor.insert_blocks(
 5795            [BlockProperties {
 5796                style: BlockStyle::Fixed,
 5797                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5798                height: Some(1),
 5799                render: Arc::new(|_| div().into_any()),
 5800                priority: 0,
 5801            }],
 5802            Some(Autoscroll::fit()),
 5803            cx,
 5804        );
 5805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5806            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5807        });
 5808        editor.move_line_down(&MoveLineDown, window, cx);
 5809    });
 5810}
 5811
 5812#[gpui::test]
 5813async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5814    init_test(cx, |_| {});
 5815
 5816    let mut cx = EditorTestContext::new(cx).await;
 5817    cx.set_state(
 5818        &"
 5819            ˇzero
 5820            one
 5821            two
 5822            three
 5823            four
 5824            five
 5825        "
 5826        .unindent(),
 5827    );
 5828
 5829    // Create a four-line block that replaces three lines of text.
 5830    cx.update_editor(|editor, window, cx| {
 5831        let snapshot = editor.snapshot(window, cx);
 5832        let snapshot = &snapshot.buffer_snapshot();
 5833        let placement = BlockPlacement::Replace(
 5834            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5835        );
 5836        editor.insert_blocks(
 5837            [BlockProperties {
 5838                placement,
 5839                height: Some(4),
 5840                style: BlockStyle::Sticky,
 5841                render: Arc::new(|_| gpui::div().into_any_element()),
 5842                priority: 0,
 5843            }],
 5844            None,
 5845            cx,
 5846        );
 5847    });
 5848
 5849    // Move down so that the cursor touches the block.
 5850    cx.update_editor(|editor, window, cx| {
 5851        editor.move_down(&Default::default(), window, cx);
 5852    });
 5853    cx.assert_editor_state(
 5854        &"
 5855            zero
 5856            «one
 5857            two
 5858            threeˇ»
 5859            four
 5860            five
 5861        "
 5862        .unindent(),
 5863    );
 5864
 5865    // Move down past the block.
 5866    cx.update_editor(|editor, window, cx| {
 5867        editor.move_down(&Default::default(), window, cx);
 5868    });
 5869    cx.assert_editor_state(
 5870        &"
 5871            zero
 5872            one
 5873            two
 5874            three
 5875            ˇfour
 5876            five
 5877        "
 5878        .unindent(),
 5879    );
 5880}
 5881
 5882#[gpui::test]
 5883fn test_transpose(cx: &mut TestAppContext) {
 5884    init_test(cx, |_| {});
 5885
 5886    _ = cx.add_window(|window, cx| {
 5887        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5888        editor.set_style(EditorStyle::default(), window, cx);
 5889        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5890            s.select_ranges([1..1])
 5891        });
 5892        editor.transpose(&Default::default(), window, cx);
 5893        assert_eq!(editor.text(cx), "bac");
 5894        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5895
 5896        editor.transpose(&Default::default(), window, cx);
 5897        assert_eq!(editor.text(cx), "bca");
 5898        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5899
 5900        editor.transpose(&Default::default(), window, cx);
 5901        assert_eq!(editor.text(cx), "bac");
 5902        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5903
 5904        editor
 5905    });
 5906
 5907    _ = cx.add_window(|window, cx| {
 5908        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5909        editor.set_style(EditorStyle::default(), window, cx);
 5910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5911            s.select_ranges([3..3])
 5912        });
 5913        editor.transpose(&Default::default(), window, cx);
 5914        assert_eq!(editor.text(cx), "acb\nde");
 5915        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5916
 5917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5918            s.select_ranges([4..4])
 5919        });
 5920        editor.transpose(&Default::default(), window, cx);
 5921        assert_eq!(editor.text(cx), "acbd\ne");
 5922        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5923
 5924        editor.transpose(&Default::default(), window, cx);
 5925        assert_eq!(editor.text(cx), "acbde\n");
 5926        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5927
 5928        editor.transpose(&Default::default(), window, cx);
 5929        assert_eq!(editor.text(cx), "acbd\ne");
 5930        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5931
 5932        editor
 5933    });
 5934
 5935    _ = cx.add_window(|window, cx| {
 5936        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5937        editor.set_style(EditorStyle::default(), window, cx);
 5938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5939            s.select_ranges([1..1, 2..2, 4..4])
 5940        });
 5941        editor.transpose(&Default::default(), window, cx);
 5942        assert_eq!(editor.text(cx), "bacd\ne");
 5943        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5944
 5945        editor.transpose(&Default::default(), window, cx);
 5946        assert_eq!(editor.text(cx), "bcade\n");
 5947        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5948
 5949        editor.transpose(&Default::default(), window, cx);
 5950        assert_eq!(editor.text(cx), "bcda\ne");
 5951        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5952
 5953        editor.transpose(&Default::default(), window, cx);
 5954        assert_eq!(editor.text(cx), "bcade\n");
 5955        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5956
 5957        editor.transpose(&Default::default(), window, cx);
 5958        assert_eq!(editor.text(cx), "bcaed\n");
 5959        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5960
 5961        editor
 5962    });
 5963
 5964    _ = cx.add_window(|window, cx| {
 5965        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5966        editor.set_style(EditorStyle::default(), window, cx);
 5967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5968            s.select_ranges([4..4])
 5969        });
 5970        editor.transpose(&Default::default(), window, cx);
 5971        assert_eq!(editor.text(cx), "🏀🍐✋");
 5972        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5973
 5974        editor.transpose(&Default::default(), window, cx);
 5975        assert_eq!(editor.text(cx), "🏀✋🍐");
 5976        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5977
 5978        editor.transpose(&Default::default(), window, cx);
 5979        assert_eq!(editor.text(cx), "🏀🍐✋");
 5980        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5981
 5982        editor
 5983    });
 5984}
 5985
 5986#[gpui::test]
 5987async fn test_rewrap(cx: &mut TestAppContext) {
 5988    init_test(cx, |settings| {
 5989        settings.languages.0.extend([
 5990            (
 5991                "Markdown".into(),
 5992                LanguageSettingsContent {
 5993                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5994                    preferred_line_length: Some(40),
 5995                    ..Default::default()
 5996                },
 5997            ),
 5998            (
 5999                "Plain Text".into(),
 6000                LanguageSettingsContent {
 6001                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6002                    preferred_line_length: Some(40),
 6003                    ..Default::default()
 6004                },
 6005            ),
 6006            (
 6007                "C++".into(),
 6008                LanguageSettingsContent {
 6009                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6010                    preferred_line_length: Some(40),
 6011                    ..Default::default()
 6012                },
 6013            ),
 6014            (
 6015                "Python".into(),
 6016                LanguageSettingsContent {
 6017                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6018                    preferred_line_length: Some(40),
 6019                    ..Default::default()
 6020                },
 6021            ),
 6022            (
 6023                "Rust".into(),
 6024                LanguageSettingsContent {
 6025                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6026                    preferred_line_length: Some(40),
 6027                    ..Default::default()
 6028                },
 6029            ),
 6030        ])
 6031    });
 6032
 6033    let mut cx = EditorTestContext::new(cx).await;
 6034
 6035    let cpp_language = Arc::new(Language::new(
 6036        LanguageConfig {
 6037            name: "C++".into(),
 6038            line_comments: vec!["// ".into()],
 6039            ..LanguageConfig::default()
 6040        },
 6041        None,
 6042    ));
 6043    let python_language = Arc::new(Language::new(
 6044        LanguageConfig {
 6045            name: "Python".into(),
 6046            line_comments: vec!["# ".into()],
 6047            ..LanguageConfig::default()
 6048        },
 6049        None,
 6050    ));
 6051    let markdown_language = Arc::new(Language::new(
 6052        LanguageConfig {
 6053            name: "Markdown".into(),
 6054            rewrap_prefixes: vec![
 6055                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6056                regex::Regex::new("[-*+]\\s+").unwrap(),
 6057            ],
 6058            ..LanguageConfig::default()
 6059        },
 6060        None,
 6061    ));
 6062    let rust_language = Arc::new(
 6063        Language::new(
 6064            LanguageConfig {
 6065                name: "Rust".into(),
 6066                line_comments: vec!["// ".into(), "/// ".into()],
 6067                ..LanguageConfig::default()
 6068            },
 6069            Some(tree_sitter_rust::LANGUAGE.into()),
 6070        )
 6071        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6072        .unwrap(),
 6073    );
 6074
 6075    let plaintext_language = Arc::new(Language::new(
 6076        LanguageConfig {
 6077            name: "Plain Text".into(),
 6078            ..LanguageConfig::default()
 6079        },
 6080        None,
 6081    ));
 6082
 6083    // Test basic rewrapping of a long line with a cursor
 6084    assert_rewrap(
 6085        indoc! {"
 6086            // ˇThis is a long comment that needs to be wrapped.
 6087        "},
 6088        indoc! {"
 6089            // ˇThis is a long comment that needs to
 6090            // be wrapped.
 6091        "},
 6092        cpp_language.clone(),
 6093        &mut cx,
 6094    );
 6095
 6096    // Test rewrapping a full selection
 6097    assert_rewrap(
 6098        indoc! {"
 6099            «// This selected long comment needs to be wrapped.ˇ»"
 6100        },
 6101        indoc! {"
 6102            «// This selected long comment needs to
 6103            // be wrapped.ˇ»"
 6104        },
 6105        cpp_language.clone(),
 6106        &mut cx,
 6107    );
 6108
 6109    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6110    assert_rewrap(
 6111        indoc! {"
 6112            // ˇThis is the first line.
 6113            // Thisˇ is the second line.
 6114            // This is the thirdˇ line, all part of one paragraph.
 6115         "},
 6116        indoc! {"
 6117            // ˇThis is the first line. Thisˇ is the
 6118            // second line. This is the thirdˇ line,
 6119            // all part of one paragraph.
 6120         "},
 6121        cpp_language.clone(),
 6122        &mut cx,
 6123    );
 6124
 6125    // Test multiple cursors in different paragraphs trigger separate rewraps
 6126    assert_rewrap(
 6127        indoc! {"
 6128            // ˇThis is the first paragraph, first line.
 6129            // ˇThis is the first paragraph, second line.
 6130
 6131            // ˇThis is the second paragraph, first line.
 6132            // ˇThis is the second paragraph, second line.
 6133        "},
 6134        indoc! {"
 6135            // ˇThis is the first paragraph, first
 6136            // line. ˇThis is the first paragraph,
 6137            // second line.
 6138
 6139            // ˇThis is the second paragraph, first
 6140            // line. ˇThis is the second paragraph,
 6141            // second line.
 6142        "},
 6143        cpp_language.clone(),
 6144        &mut cx,
 6145    );
 6146
 6147    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6148    assert_rewrap(
 6149        indoc! {"
 6150            «// A regular long long comment to be wrapped.
 6151            /// A documentation long comment to be wrapped.ˇ»
 6152          "},
 6153        indoc! {"
 6154            «// A regular long long comment to be
 6155            // wrapped.
 6156            /// A documentation long comment to be
 6157            /// wrapped.ˇ»
 6158          "},
 6159        rust_language.clone(),
 6160        &mut cx,
 6161    );
 6162
 6163    // Test that change in indentation level trigger seperate rewraps
 6164    assert_rewrap(
 6165        indoc! {"
 6166            fn foo() {
 6167                «// This is a long comment at the base indent.
 6168                    // This is a long comment at the next indent.ˇ»
 6169            }
 6170        "},
 6171        indoc! {"
 6172            fn foo() {
 6173                «// This is a long comment at the
 6174                // base indent.
 6175                    // This is a long comment at the
 6176                    // next indent.ˇ»
 6177            }
 6178        "},
 6179        rust_language.clone(),
 6180        &mut cx,
 6181    );
 6182
 6183    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6184    assert_rewrap(
 6185        indoc! {"
 6186            # ˇThis is a long comment using a pound sign.
 6187        "},
 6188        indoc! {"
 6189            # ˇThis is a long comment using a pound
 6190            # sign.
 6191        "},
 6192        python_language,
 6193        &mut cx,
 6194    );
 6195
 6196    // Test rewrapping only affects comments, not code even when selected
 6197    assert_rewrap(
 6198        indoc! {"
 6199            «/// This doc comment is long and should be wrapped.
 6200            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6201        "},
 6202        indoc! {"
 6203            «/// This doc comment is long and should
 6204            /// be wrapped.
 6205            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6206        "},
 6207        rust_language.clone(),
 6208        &mut cx,
 6209    );
 6210
 6211    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6212    assert_rewrap(
 6213        indoc! {"
 6214            # Header
 6215
 6216            A long long long line of markdown text to wrap.ˇ
 6217         "},
 6218        indoc! {"
 6219            # Header
 6220
 6221            A long long long line of markdown text
 6222            to wrap.ˇ
 6223         "},
 6224        markdown_language.clone(),
 6225        &mut cx,
 6226    );
 6227
 6228    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6229    assert_rewrap(
 6230        indoc! {"
 6231            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6232            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6233            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6234        "},
 6235        indoc! {"
 6236            «1. This is a numbered list item that is
 6237               very long and needs to be wrapped
 6238               properly.
 6239            2. This is a numbered list item that is
 6240               very long and needs to be wrapped
 6241               properly.
 6242            - This is an unordered list item that is
 6243              also very long and should not merge
 6244              with the numbered item.ˇ»
 6245        "},
 6246        markdown_language.clone(),
 6247        &mut cx,
 6248    );
 6249
 6250    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6251    assert_rewrap(
 6252        indoc! {"
 6253            «1. This is a numbered list item that is
 6254            very long and needs to be wrapped
 6255            properly.
 6256            2. This is a numbered list item that is
 6257            very long and needs to be wrapped
 6258            properly.
 6259            - This is an unordered list item that is
 6260            also very long and should not merge with
 6261            the numbered item.ˇ»
 6262        "},
 6263        indoc! {"
 6264            «1. This is a numbered list item that is
 6265               very long and needs to be wrapped
 6266               properly.
 6267            2. This is a numbered list item that is
 6268               very long and needs to be wrapped
 6269               properly.
 6270            - This is an unordered list item that is
 6271              also very long and should not merge
 6272              with the numbered item.ˇ»
 6273        "},
 6274        markdown_language.clone(),
 6275        &mut cx,
 6276    );
 6277
 6278    // Test that rewrapping maintain indents even when they already exists.
 6279    assert_rewrap(
 6280        indoc! {"
 6281            «1. This is a numbered list
 6282               item that is very long and needs to be wrapped properly.
 6283            2. This is a numbered list
 6284               item that is very long and needs to be wrapped properly.
 6285            - This is an unordered list item that is also very long and
 6286              should not merge with the numbered item.ˇ»
 6287        "},
 6288        indoc! {"
 6289            «1. This is a numbered list item that is
 6290               very long and needs to be wrapped
 6291               properly.
 6292            2. This is a numbered list item that is
 6293               very long and needs to be wrapped
 6294               properly.
 6295            - This is an unordered list item that is
 6296              also very long and should not merge
 6297              with the numbered item.ˇ»
 6298        "},
 6299        markdown_language,
 6300        &mut cx,
 6301    );
 6302
 6303    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6304    assert_rewrap(
 6305        indoc! {"
 6306            ˇThis is a very long line of plain text that will be wrapped.
 6307        "},
 6308        indoc! {"
 6309            ˇThis is a very long line of plain text
 6310            that will be wrapped.
 6311        "},
 6312        plaintext_language.clone(),
 6313        &mut cx,
 6314    );
 6315
 6316    // Test that non-commented code acts as a paragraph boundary within a selection
 6317    assert_rewrap(
 6318        indoc! {"
 6319               «// This is the first long comment block to be wrapped.
 6320               fn my_func(a: u32);
 6321               // This is the second long comment block to be wrapped.ˇ»
 6322           "},
 6323        indoc! {"
 6324               «// This is the first long comment block
 6325               // to be wrapped.
 6326               fn my_func(a: u32);
 6327               // This is the second long comment block
 6328               // to be wrapped.ˇ»
 6329           "},
 6330        rust_language,
 6331        &mut cx,
 6332    );
 6333
 6334    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6335    assert_rewrap(
 6336        indoc! {"
 6337            «ˇThis is a very long line that will be wrapped.
 6338
 6339            This is another paragraph in the same selection.»
 6340
 6341            «\tThis is a very long indented line that will be wrapped.ˇ»
 6342         "},
 6343        indoc! {"
 6344            «ˇThis is a very long line that will be
 6345            wrapped.
 6346
 6347            This is another paragraph in the same
 6348            selection.»
 6349
 6350            «\tThis is a very long indented line
 6351            \tthat will be wrapped.ˇ»
 6352         "},
 6353        plaintext_language,
 6354        &mut cx,
 6355    );
 6356
 6357    // Test that an empty comment line acts as a paragraph boundary
 6358    assert_rewrap(
 6359        indoc! {"
 6360            // ˇThis is a long comment that will be wrapped.
 6361            //
 6362            // And this is another long comment that will also be wrapped.ˇ
 6363         "},
 6364        indoc! {"
 6365            // ˇThis is a long comment that will be
 6366            // wrapped.
 6367            //
 6368            // And this is another long comment that
 6369            // will also be wrapped.ˇ
 6370         "},
 6371        cpp_language,
 6372        &mut cx,
 6373    );
 6374
 6375    #[track_caller]
 6376    fn assert_rewrap(
 6377        unwrapped_text: &str,
 6378        wrapped_text: &str,
 6379        language: Arc<Language>,
 6380        cx: &mut EditorTestContext,
 6381    ) {
 6382        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6383        cx.set_state(unwrapped_text);
 6384        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6385        cx.assert_editor_state(wrapped_text);
 6386    }
 6387}
 6388
 6389#[gpui::test]
 6390async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6391    init_test(cx, |settings| {
 6392        settings.languages.0.extend([(
 6393            "Rust".into(),
 6394            LanguageSettingsContent {
 6395                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6396                preferred_line_length: Some(40),
 6397                ..Default::default()
 6398            },
 6399        )])
 6400    });
 6401
 6402    let mut cx = EditorTestContext::new(cx).await;
 6403
 6404    let rust_lang = Arc::new(
 6405        Language::new(
 6406            LanguageConfig {
 6407                name: "Rust".into(),
 6408                line_comments: vec!["// ".into()],
 6409                block_comment: Some(BlockCommentConfig {
 6410                    start: "/*".into(),
 6411                    end: "*/".into(),
 6412                    prefix: "* ".into(),
 6413                    tab_size: 1,
 6414                }),
 6415                documentation_comment: Some(BlockCommentConfig {
 6416                    start: "/**".into(),
 6417                    end: "*/".into(),
 6418                    prefix: "* ".into(),
 6419                    tab_size: 1,
 6420                }),
 6421
 6422                ..LanguageConfig::default()
 6423            },
 6424            Some(tree_sitter_rust::LANGUAGE.into()),
 6425        )
 6426        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6427        .unwrap(),
 6428    );
 6429
 6430    // regular block comment
 6431    assert_rewrap(
 6432        indoc! {"
 6433            /*
 6434             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6435             */
 6436            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6437        "},
 6438        indoc! {"
 6439            /*
 6440             *ˇ Lorem ipsum dolor sit amet,
 6441             * consectetur adipiscing elit.
 6442             */
 6443            /*
 6444             *ˇ Lorem ipsum dolor sit amet,
 6445             * consectetur adipiscing elit.
 6446             */
 6447        "},
 6448        rust_lang.clone(),
 6449        &mut cx,
 6450    );
 6451
 6452    // indent is respected
 6453    assert_rewrap(
 6454        indoc! {"
 6455            {}
 6456                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6457        "},
 6458        indoc! {"
 6459            {}
 6460                /*
 6461                 *ˇ Lorem ipsum dolor sit amet,
 6462                 * consectetur adipiscing elit.
 6463                 */
 6464        "},
 6465        rust_lang.clone(),
 6466        &mut cx,
 6467    );
 6468
 6469    // short block comments with inline delimiters
 6470    assert_rewrap(
 6471        indoc! {"
 6472            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6473            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6474             */
 6475            /*
 6476             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6477        "},
 6478        indoc! {"
 6479            /*
 6480             *ˇ Lorem ipsum dolor sit amet,
 6481             * consectetur adipiscing elit.
 6482             */
 6483            /*
 6484             *ˇ Lorem ipsum dolor sit amet,
 6485             * consectetur adipiscing elit.
 6486             */
 6487            /*
 6488             *ˇ Lorem ipsum dolor sit amet,
 6489             * consectetur adipiscing elit.
 6490             */
 6491        "},
 6492        rust_lang.clone(),
 6493        &mut cx,
 6494    );
 6495
 6496    // multiline block comment with inline start/end delimiters
 6497    assert_rewrap(
 6498        indoc! {"
 6499            /*ˇ Lorem ipsum dolor sit amet,
 6500             * consectetur adipiscing elit. */
 6501        "},
 6502        indoc! {"
 6503            /*
 6504             *ˇ Lorem ipsum dolor sit amet,
 6505             * consectetur adipiscing elit.
 6506             */
 6507        "},
 6508        rust_lang.clone(),
 6509        &mut cx,
 6510    );
 6511
 6512    // block comment rewrap still respects paragraph bounds
 6513    assert_rewrap(
 6514        indoc! {"
 6515            /*
 6516             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6517             *
 6518             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6519             */
 6520        "},
 6521        indoc! {"
 6522            /*
 6523             *ˇ Lorem ipsum dolor sit amet,
 6524             * consectetur adipiscing elit.
 6525             *
 6526             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6527             */
 6528        "},
 6529        rust_lang.clone(),
 6530        &mut cx,
 6531    );
 6532
 6533    // documentation comments
 6534    assert_rewrap(
 6535        indoc! {"
 6536            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6537            /**
 6538             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6539             */
 6540        "},
 6541        indoc! {"
 6542            /**
 6543             *ˇ Lorem ipsum dolor sit amet,
 6544             * consectetur adipiscing elit.
 6545             */
 6546            /**
 6547             *ˇ Lorem ipsum dolor sit amet,
 6548             * consectetur adipiscing elit.
 6549             */
 6550        "},
 6551        rust_lang.clone(),
 6552        &mut cx,
 6553    );
 6554
 6555    // different, adjacent comments
 6556    assert_rewrap(
 6557        indoc! {"
 6558            /**
 6559             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6560             */
 6561            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6562            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6563        "},
 6564        indoc! {"
 6565            /**
 6566             *ˇ Lorem ipsum dolor sit amet,
 6567             * consectetur adipiscing elit.
 6568             */
 6569            /*
 6570             *ˇ Lorem ipsum dolor sit amet,
 6571             * consectetur adipiscing elit.
 6572             */
 6573            //ˇ Lorem ipsum dolor sit amet,
 6574            // consectetur adipiscing elit.
 6575        "},
 6576        rust_lang.clone(),
 6577        &mut cx,
 6578    );
 6579
 6580    // selection w/ single short block comment
 6581    assert_rewrap(
 6582        indoc! {"
 6583            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6584        "},
 6585        indoc! {"
 6586            «/*
 6587             * Lorem ipsum dolor sit amet,
 6588             * consectetur adipiscing elit.
 6589             */ˇ»
 6590        "},
 6591        rust_lang.clone(),
 6592        &mut cx,
 6593    );
 6594
 6595    // rewrapping a single comment w/ abutting comments
 6596    assert_rewrap(
 6597        indoc! {"
 6598            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6599            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6600        "},
 6601        indoc! {"
 6602            /*
 6603             * ˇLorem ipsum dolor sit amet,
 6604             * consectetur adipiscing elit.
 6605             */
 6606            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6607        "},
 6608        rust_lang.clone(),
 6609        &mut cx,
 6610    );
 6611
 6612    // selection w/ non-abutting short block comments
 6613    assert_rewrap(
 6614        indoc! {"
 6615            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6616
 6617            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6618        "},
 6619        indoc! {"
 6620            «/*
 6621             * Lorem ipsum dolor sit amet,
 6622             * consectetur adipiscing elit.
 6623             */
 6624
 6625            /*
 6626             * Lorem ipsum dolor sit amet,
 6627             * consectetur adipiscing elit.
 6628             */ˇ»
 6629        "},
 6630        rust_lang.clone(),
 6631        &mut cx,
 6632    );
 6633
 6634    // selection of multiline block comments
 6635    assert_rewrap(
 6636        indoc! {"
 6637            «/* Lorem ipsum dolor sit amet,
 6638             * consectetur adipiscing elit. */ˇ»
 6639        "},
 6640        indoc! {"
 6641            «/*
 6642             * Lorem ipsum dolor sit amet,
 6643             * consectetur adipiscing elit.
 6644             */ˇ»
 6645        "},
 6646        rust_lang.clone(),
 6647        &mut cx,
 6648    );
 6649
 6650    // partial selection of multiline block comments
 6651    assert_rewrap(
 6652        indoc! {"
 6653            «/* Lorem ipsum dolor sit amet,ˇ»
 6654             * consectetur adipiscing elit. */
 6655            /* Lorem ipsum dolor sit amet,
 6656             «* consectetur adipiscing elit. */ˇ»
 6657        "},
 6658        indoc! {"
 6659            «/*
 6660             * Lorem ipsum dolor sit amet,ˇ»
 6661             * consectetur adipiscing elit. */
 6662            /* Lorem ipsum dolor sit amet,
 6663             «* consectetur adipiscing elit.
 6664             */ˇ»
 6665        "},
 6666        rust_lang.clone(),
 6667        &mut cx,
 6668    );
 6669
 6670    // selection w/ abutting short block comments
 6671    // TODO: should not be combined; should rewrap as 2 comments
 6672    assert_rewrap(
 6673        indoc! {"
 6674            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6675            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6676        "},
 6677        // desired behavior:
 6678        // indoc! {"
 6679        //     «/*
 6680        //      * Lorem ipsum dolor sit amet,
 6681        //      * consectetur adipiscing elit.
 6682        //      */
 6683        //     /*
 6684        //      * Lorem ipsum dolor sit amet,
 6685        //      * consectetur adipiscing elit.
 6686        //      */ˇ»
 6687        // "},
 6688        // actual behaviour:
 6689        indoc! {"
 6690            «/*
 6691             * Lorem ipsum dolor sit amet,
 6692             * consectetur adipiscing elit. Lorem
 6693             * ipsum dolor sit amet, consectetur
 6694             * adipiscing elit.
 6695             */ˇ»
 6696        "},
 6697        rust_lang.clone(),
 6698        &mut cx,
 6699    );
 6700
 6701    // TODO: same as above, but with delimiters on separate line
 6702    // assert_rewrap(
 6703    //     indoc! {"
 6704    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6705    //          */
 6706    //         /*
 6707    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6708    //     "},
 6709    //     // desired:
 6710    //     // indoc! {"
 6711    //     //     «/*
 6712    //     //      * Lorem ipsum dolor sit amet,
 6713    //     //      * consectetur adipiscing elit.
 6714    //     //      */
 6715    //     //     /*
 6716    //     //      * Lorem ipsum dolor sit amet,
 6717    //     //      * consectetur adipiscing elit.
 6718    //     //      */ˇ»
 6719    //     // "},
 6720    //     // actual: (but with trailing w/s on the empty lines)
 6721    //     indoc! {"
 6722    //         «/*
 6723    //          * Lorem ipsum dolor sit amet,
 6724    //          * consectetur adipiscing elit.
 6725    //          *
 6726    //          */
 6727    //         /*
 6728    //          *
 6729    //          * Lorem ipsum dolor sit amet,
 6730    //          * consectetur adipiscing elit.
 6731    //          */ˇ»
 6732    //     "},
 6733    //     rust_lang.clone(),
 6734    //     &mut cx,
 6735    // );
 6736
 6737    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6738    assert_rewrap(
 6739        indoc! {"
 6740            /*
 6741             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6742             */
 6743            /*
 6744             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6745            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6746        "},
 6747        // desired:
 6748        // indoc! {"
 6749        //     /*
 6750        //      *ˇ Lorem ipsum dolor sit amet,
 6751        //      * consectetur adipiscing elit.
 6752        //      */
 6753        //     /*
 6754        //      *ˇ Lorem ipsum dolor sit amet,
 6755        //      * consectetur adipiscing elit.
 6756        //      */
 6757        //     /*
 6758        //      *ˇ Lorem ipsum dolor sit amet
 6759        //      */ /* consectetur adipiscing elit. */
 6760        // "},
 6761        // actual:
 6762        indoc! {"
 6763            /*
 6764             //ˇ Lorem ipsum dolor sit amet,
 6765             // consectetur adipiscing elit.
 6766             */
 6767            /*
 6768             * //ˇ Lorem ipsum dolor sit amet,
 6769             * consectetur adipiscing elit.
 6770             */
 6771            /*
 6772             *ˇ Lorem ipsum dolor sit amet */ /*
 6773             * consectetur adipiscing elit.
 6774             */
 6775        "},
 6776        rust_lang,
 6777        &mut cx,
 6778    );
 6779
 6780    #[track_caller]
 6781    fn assert_rewrap(
 6782        unwrapped_text: &str,
 6783        wrapped_text: &str,
 6784        language: Arc<Language>,
 6785        cx: &mut EditorTestContext,
 6786    ) {
 6787        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6788        cx.set_state(unwrapped_text);
 6789        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6790        cx.assert_editor_state(wrapped_text);
 6791    }
 6792}
 6793
 6794#[gpui::test]
 6795async fn test_hard_wrap(cx: &mut TestAppContext) {
 6796    init_test(cx, |_| {});
 6797    let mut cx = EditorTestContext::new(cx).await;
 6798
 6799    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6800    cx.update_editor(|editor, _, cx| {
 6801        editor.set_hard_wrap(Some(14), cx);
 6802    });
 6803
 6804    cx.set_state(indoc!(
 6805        "
 6806        one two three ˇ
 6807        "
 6808    ));
 6809    cx.simulate_input("four");
 6810    cx.run_until_parked();
 6811
 6812    cx.assert_editor_state(indoc!(
 6813        "
 6814        one two three
 6815        fourˇ
 6816        "
 6817    ));
 6818
 6819    cx.update_editor(|editor, window, cx| {
 6820        editor.newline(&Default::default(), window, cx);
 6821    });
 6822    cx.run_until_parked();
 6823    cx.assert_editor_state(indoc!(
 6824        "
 6825        one two three
 6826        four
 6827        ˇ
 6828        "
 6829    ));
 6830
 6831    cx.simulate_input("five");
 6832    cx.run_until_parked();
 6833    cx.assert_editor_state(indoc!(
 6834        "
 6835        one two three
 6836        four
 6837        fiveˇ
 6838        "
 6839    ));
 6840
 6841    cx.update_editor(|editor, window, cx| {
 6842        editor.newline(&Default::default(), window, cx);
 6843    });
 6844    cx.run_until_parked();
 6845    cx.simulate_input("# ");
 6846    cx.run_until_parked();
 6847    cx.assert_editor_state(indoc!(
 6848        "
 6849        one two three
 6850        four
 6851        five
 6852        # ˇ
 6853        "
 6854    ));
 6855
 6856    cx.update_editor(|editor, window, cx| {
 6857        editor.newline(&Default::default(), window, cx);
 6858    });
 6859    cx.run_until_parked();
 6860    cx.assert_editor_state(indoc!(
 6861        "
 6862        one two three
 6863        four
 6864        five
 6865        #\x20
 6866 6867        "
 6868    ));
 6869
 6870    cx.simulate_input(" 6");
 6871    cx.run_until_parked();
 6872    cx.assert_editor_state(indoc!(
 6873        "
 6874        one two three
 6875        four
 6876        five
 6877        #
 6878        # 6ˇ
 6879        "
 6880    ));
 6881}
 6882
 6883#[gpui::test]
 6884async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6885    init_test(cx, |_| {});
 6886
 6887    let mut cx = EditorTestContext::new(cx).await;
 6888
 6889    cx.set_state(indoc! {"The quick brownˇ"});
 6890    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6891    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6892
 6893    cx.set_state(indoc! {"The emacs foxˇ"});
 6894    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6895    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6896
 6897    cx.set_state(indoc! {"
 6898        The quick« brownˇ»
 6899        fox jumps overˇ
 6900        the lazy dog"});
 6901    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6902    cx.assert_editor_state(indoc! {"
 6903        The quickˇ
 6904        ˇthe lazy dog"});
 6905
 6906    cx.set_state(indoc! {"
 6907        The quick« brownˇ»
 6908        fox jumps overˇ
 6909        the lazy dog"});
 6910    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6911    cx.assert_editor_state(indoc! {"
 6912        The quickˇ
 6913        fox jumps overˇthe lazy dog"});
 6914
 6915    cx.set_state(indoc! {"
 6916        The quick« brownˇ»
 6917        fox jumps overˇ
 6918        the lazy dog"});
 6919    cx.update_editor(|e, window, cx| {
 6920        e.cut_to_end_of_line(
 6921            &CutToEndOfLine {
 6922                stop_at_newlines: true,
 6923            },
 6924            window,
 6925            cx,
 6926        )
 6927    });
 6928    cx.assert_editor_state(indoc! {"
 6929        The quickˇ
 6930        fox jumps overˇ
 6931        the lazy dog"});
 6932
 6933    cx.set_state(indoc! {"
 6934        The quick« brownˇ»
 6935        fox jumps overˇ
 6936        the lazy dog"});
 6937    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6938    cx.assert_editor_state(indoc! {"
 6939        The quickˇ
 6940        fox jumps overˇthe lazy dog"});
 6941}
 6942
 6943#[gpui::test]
 6944async fn test_clipboard(cx: &mut TestAppContext) {
 6945    init_test(cx, |_| {});
 6946
 6947    let mut cx = EditorTestContext::new(cx).await;
 6948
 6949    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6950    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6951    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6952
 6953    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6954    cx.set_state("two ˇfour ˇsix ˇ");
 6955    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6956    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6957
 6958    // Paste again but with only two cursors. Since the number of cursors doesn't
 6959    // match the number of slices in the clipboard, the entire clipboard text
 6960    // is pasted at each cursor.
 6961    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6962    cx.update_editor(|e, window, cx| {
 6963        e.handle_input("( ", window, cx);
 6964        e.paste(&Paste, window, cx);
 6965        e.handle_input(") ", window, cx);
 6966    });
 6967    cx.assert_editor_state(
 6968        &([
 6969            "( one✅ ",
 6970            "three ",
 6971            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6972            "three ",
 6973            "five ) ˇ",
 6974        ]
 6975        .join("\n")),
 6976    );
 6977
 6978    // Cut with three selections, one of which is full-line.
 6979    cx.set_state(indoc! {"
 6980        1«2ˇ»3
 6981        4ˇ567
 6982        «8ˇ»9"});
 6983    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6984    cx.assert_editor_state(indoc! {"
 6985        1ˇ3
 6986        ˇ9"});
 6987
 6988    // Paste with three selections, noticing how the copied selection that was full-line
 6989    // gets inserted before the second cursor.
 6990    cx.set_state(indoc! {"
 6991        1ˇ3
 6992 6993        «oˇ»ne"});
 6994    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6995    cx.assert_editor_state(indoc! {"
 6996        12ˇ3
 6997        4567
 6998 6999        8ˇne"});
 7000
 7001    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7002    cx.set_state(indoc! {"
 7003        The quick brown
 7004        fox juˇmps over
 7005        the lazy dog"});
 7006    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7007    assert_eq!(
 7008        cx.read_from_clipboard()
 7009            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7010        Some("fox jumps over\n".to_string())
 7011    );
 7012
 7013    // Paste with three selections, noticing how the copied full-line selection is inserted
 7014    // before the empty selections but replaces the selection that is non-empty.
 7015    cx.set_state(indoc! {"
 7016        Tˇhe quick brown
 7017        «foˇ»x jumps over
 7018        tˇhe lazy dog"});
 7019    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7020    cx.assert_editor_state(indoc! {"
 7021        fox jumps over
 7022        Tˇhe quick brown
 7023        fox jumps over
 7024        ˇx jumps over
 7025        fox jumps over
 7026        tˇhe lazy dog"});
 7027}
 7028
 7029#[gpui::test]
 7030async fn test_copy_trim(cx: &mut TestAppContext) {
 7031    init_test(cx, |_| {});
 7032
 7033    let mut cx = EditorTestContext::new(cx).await;
 7034    cx.set_state(
 7035        r#"            «for selection in selections.iter() {
 7036            let mut start = selection.start;
 7037            let mut end = selection.end;
 7038            let is_entire_line = selection.is_empty();
 7039            if is_entire_line {
 7040                start = Point::new(start.row, 0);ˇ»
 7041                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7042            }
 7043        "#,
 7044    );
 7045    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7046    assert_eq!(
 7047        cx.read_from_clipboard()
 7048            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7049        Some(
 7050            "for selection in selections.iter() {
 7051            let mut start = selection.start;
 7052            let mut end = selection.end;
 7053            let is_entire_line = selection.is_empty();
 7054            if is_entire_line {
 7055                start = Point::new(start.row, 0);"
 7056                .to_string()
 7057        ),
 7058        "Regular copying preserves all indentation selected",
 7059    );
 7060    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7061    assert_eq!(
 7062        cx.read_from_clipboard()
 7063            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7064        Some(
 7065            "for selection in selections.iter() {
 7066let mut start = selection.start;
 7067let mut end = selection.end;
 7068let is_entire_line = selection.is_empty();
 7069if is_entire_line {
 7070    start = Point::new(start.row, 0);"
 7071                .to_string()
 7072        ),
 7073        "Copying with stripping should strip all leading whitespaces"
 7074    );
 7075
 7076    cx.set_state(
 7077        r#"       «     for selection in selections.iter() {
 7078            let mut start = selection.start;
 7079            let mut end = selection.end;
 7080            let is_entire_line = selection.is_empty();
 7081            if is_entire_line {
 7082                start = Point::new(start.row, 0);ˇ»
 7083                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7084            }
 7085        "#,
 7086    );
 7087    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7088    assert_eq!(
 7089        cx.read_from_clipboard()
 7090            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7091        Some(
 7092            "     for selection in selections.iter() {
 7093            let mut start = selection.start;
 7094            let mut end = selection.end;
 7095            let is_entire_line = selection.is_empty();
 7096            if is_entire_line {
 7097                start = Point::new(start.row, 0);"
 7098                .to_string()
 7099        ),
 7100        "Regular copying preserves all indentation selected",
 7101    );
 7102    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7103    assert_eq!(
 7104        cx.read_from_clipboard()
 7105            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7106        Some(
 7107            "for selection in selections.iter() {
 7108let mut start = selection.start;
 7109let mut end = selection.end;
 7110let is_entire_line = selection.is_empty();
 7111if is_entire_line {
 7112    start = Point::new(start.row, 0);"
 7113                .to_string()
 7114        ),
 7115        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7116    );
 7117
 7118    cx.set_state(
 7119        r#"       «ˇ     for selection in selections.iter() {
 7120            let mut start = selection.start;
 7121            let mut end = selection.end;
 7122            let is_entire_line = selection.is_empty();
 7123            if is_entire_line {
 7124                start = Point::new(start.row, 0);»
 7125                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7126            }
 7127        "#,
 7128    );
 7129    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7130    assert_eq!(
 7131        cx.read_from_clipboard()
 7132            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7133        Some(
 7134            "     for selection in selections.iter() {
 7135            let mut start = selection.start;
 7136            let mut end = selection.end;
 7137            let is_entire_line = selection.is_empty();
 7138            if is_entire_line {
 7139                start = Point::new(start.row, 0);"
 7140                .to_string()
 7141        ),
 7142        "Regular copying for reverse selection works the same",
 7143    );
 7144    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7145    assert_eq!(
 7146        cx.read_from_clipboard()
 7147            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7148        Some(
 7149            "for selection in selections.iter() {
 7150let mut start = selection.start;
 7151let mut end = selection.end;
 7152let is_entire_line = selection.is_empty();
 7153if is_entire_line {
 7154    start = Point::new(start.row, 0);"
 7155                .to_string()
 7156        ),
 7157        "Copying with stripping for reverse selection works the same"
 7158    );
 7159
 7160    cx.set_state(
 7161        r#"            for selection «in selections.iter() {
 7162            let mut start = selection.start;
 7163            let mut end = selection.end;
 7164            let is_entire_line = selection.is_empty();
 7165            if is_entire_line {
 7166                start = Point::new(start.row, 0);ˇ»
 7167                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7168            }
 7169        "#,
 7170    );
 7171    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7172    assert_eq!(
 7173        cx.read_from_clipboard()
 7174            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7175        Some(
 7176            "in selections.iter() {
 7177            let mut start = selection.start;
 7178            let mut end = selection.end;
 7179            let is_entire_line = selection.is_empty();
 7180            if is_entire_line {
 7181                start = Point::new(start.row, 0);"
 7182                .to_string()
 7183        ),
 7184        "When selecting past the indent, the copying works as usual",
 7185    );
 7186    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7187    assert_eq!(
 7188        cx.read_from_clipboard()
 7189            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7190        Some(
 7191            "in selections.iter() {
 7192            let mut start = selection.start;
 7193            let mut end = selection.end;
 7194            let is_entire_line = selection.is_empty();
 7195            if is_entire_line {
 7196                start = Point::new(start.row, 0);"
 7197                .to_string()
 7198        ),
 7199        "When selecting past the indent, nothing is trimmed"
 7200    );
 7201
 7202    cx.set_state(
 7203        r#"            «for selection in selections.iter() {
 7204            let mut start = selection.start;
 7205
 7206            let mut end = selection.end;
 7207            let is_entire_line = selection.is_empty();
 7208            if is_entire_line {
 7209                start = Point::new(start.row, 0);
 7210ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7211            }
 7212        "#,
 7213    );
 7214    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7215    assert_eq!(
 7216        cx.read_from_clipboard()
 7217            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7218        Some(
 7219            "for selection in selections.iter() {
 7220let mut start = selection.start;
 7221
 7222let mut end = selection.end;
 7223let is_entire_line = selection.is_empty();
 7224if is_entire_line {
 7225    start = Point::new(start.row, 0);
 7226"
 7227            .to_string()
 7228        ),
 7229        "Copying with stripping should ignore empty lines"
 7230    );
 7231}
 7232
 7233#[gpui::test]
 7234async fn test_paste_multiline(cx: &mut TestAppContext) {
 7235    init_test(cx, |_| {});
 7236
 7237    let mut cx = EditorTestContext::new(cx).await;
 7238    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7239
 7240    // Cut an indented block, without the leading whitespace.
 7241    cx.set_state(indoc! {"
 7242        const a: B = (
 7243            c(),
 7244            «d(
 7245                e,
 7246                f
 7247            )ˇ»
 7248        );
 7249    "});
 7250    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7251    cx.assert_editor_state(indoc! {"
 7252        const a: B = (
 7253            c(),
 7254            ˇ
 7255        );
 7256    "});
 7257
 7258    // Paste it at the same position.
 7259    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7260    cx.assert_editor_state(indoc! {"
 7261        const a: B = (
 7262            c(),
 7263            d(
 7264                e,
 7265                f
 7266 7267        );
 7268    "});
 7269
 7270    // Paste it at a line with a lower indent level.
 7271    cx.set_state(indoc! {"
 7272        ˇ
 7273        const a: B = (
 7274            c(),
 7275        );
 7276    "});
 7277    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7278    cx.assert_editor_state(indoc! {"
 7279        d(
 7280            e,
 7281            f
 7282 7283        const a: B = (
 7284            c(),
 7285        );
 7286    "});
 7287
 7288    // Cut an indented block, with the leading whitespace.
 7289    cx.set_state(indoc! {"
 7290        const a: B = (
 7291            c(),
 7292        «    d(
 7293                e,
 7294                f
 7295            )
 7296        ˇ»);
 7297    "});
 7298    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7299    cx.assert_editor_state(indoc! {"
 7300        const a: B = (
 7301            c(),
 7302        ˇ);
 7303    "});
 7304
 7305    // Paste it at the same position.
 7306    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7307    cx.assert_editor_state(indoc! {"
 7308        const a: B = (
 7309            c(),
 7310            d(
 7311                e,
 7312                f
 7313            )
 7314        ˇ);
 7315    "});
 7316
 7317    // Paste it at a line with a higher indent level.
 7318    cx.set_state(indoc! {"
 7319        const a: B = (
 7320            c(),
 7321            d(
 7322                e,
 7323 7324            )
 7325        );
 7326    "});
 7327    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7328    cx.assert_editor_state(indoc! {"
 7329        const a: B = (
 7330            c(),
 7331            d(
 7332                e,
 7333                f    d(
 7334                    e,
 7335                    f
 7336                )
 7337        ˇ
 7338            )
 7339        );
 7340    "});
 7341
 7342    // Copy an indented block, starting mid-line
 7343    cx.set_state(indoc! {"
 7344        const a: B = (
 7345            c(),
 7346            somethin«g(
 7347                e,
 7348                f
 7349            )ˇ»
 7350        );
 7351    "});
 7352    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7353
 7354    // Paste it on a line with a lower indent level
 7355    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7356    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7357    cx.assert_editor_state(indoc! {"
 7358        const a: B = (
 7359            c(),
 7360            something(
 7361                e,
 7362                f
 7363            )
 7364        );
 7365        g(
 7366            e,
 7367            f
 7368"});
 7369}
 7370
 7371#[gpui::test]
 7372async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7373    init_test(cx, |_| {});
 7374
 7375    cx.write_to_clipboard(ClipboardItem::new_string(
 7376        "    d(\n        e\n    );\n".into(),
 7377    ));
 7378
 7379    let mut cx = EditorTestContext::new(cx).await;
 7380    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7381
 7382    cx.set_state(indoc! {"
 7383        fn a() {
 7384            b();
 7385            if c() {
 7386                ˇ
 7387            }
 7388        }
 7389    "});
 7390
 7391    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7392    cx.assert_editor_state(indoc! {"
 7393        fn a() {
 7394            b();
 7395            if c() {
 7396                d(
 7397                    e
 7398                );
 7399        ˇ
 7400            }
 7401        }
 7402    "});
 7403
 7404    cx.set_state(indoc! {"
 7405        fn a() {
 7406            b();
 7407            ˇ
 7408        }
 7409    "});
 7410
 7411    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7412    cx.assert_editor_state(indoc! {"
 7413        fn a() {
 7414            b();
 7415            d(
 7416                e
 7417            );
 7418        ˇ
 7419        }
 7420    "});
 7421}
 7422
 7423#[gpui::test]
 7424fn test_select_all(cx: &mut TestAppContext) {
 7425    init_test(cx, |_| {});
 7426
 7427    let editor = cx.add_window(|window, cx| {
 7428        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7429        build_editor(buffer, window, cx)
 7430    });
 7431    _ = editor.update(cx, |editor, window, cx| {
 7432        editor.select_all(&SelectAll, window, cx);
 7433        assert_eq!(
 7434            editor.selections.display_ranges(cx),
 7435            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7436        );
 7437    });
 7438}
 7439
 7440#[gpui::test]
 7441fn test_select_line(cx: &mut TestAppContext) {
 7442    init_test(cx, |_| {});
 7443
 7444    let editor = cx.add_window(|window, cx| {
 7445        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7446        build_editor(buffer, window, cx)
 7447    });
 7448    _ = editor.update(cx, |editor, window, cx| {
 7449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7450            s.select_display_ranges([
 7451                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7452                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7453                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7454                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7455            ])
 7456        });
 7457        editor.select_line(&SelectLine, window, cx);
 7458        assert_eq!(
 7459            editor.selections.display_ranges(cx),
 7460            vec![
 7461                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7462                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7463            ]
 7464        );
 7465    });
 7466
 7467    _ = editor.update(cx, |editor, window, cx| {
 7468        editor.select_line(&SelectLine, window, cx);
 7469        assert_eq!(
 7470            editor.selections.display_ranges(cx),
 7471            vec![
 7472                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7473                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7474            ]
 7475        );
 7476    });
 7477
 7478    _ = editor.update(cx, |editor, window, cx| {
 7479        editor.select_line(&SelectLine, window, cx);
 7480        assert_eq!(
 7481            editor.selections.display_ranges(cx),
 7482            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7483        );
 7484    });
 7485}
 7486
 7487#[gpui::test]
 7488async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7489    init_test(cx, |_| {});
 7490    let mut cx = EditorTestContext::new(cx).await;
 7491
 7492    #[track_caller]
 7493    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7494        cx.set_state(initial_state);
 7495        cx.update_editor(|e, window, cx| {
 7496            e.split_selection_into_lines(&Default::default(), window, cx)
 7497        });
 7498        cx.assert_editor_state(expected_state);
 7499    }
 7500
 7501    // Selection starts and ends at the middle of lines, left-to-right
 7502    test(
 7503        &mut cx,
 7504        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7505        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7506    );
 7507    // Same thing, right-to-left
 7508    test(
 7509        &mut cx,
 7510        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7511        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7512    );
 7513
 7514    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7515    test(
 7516        &mut cx,
 7517        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7518        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7519    );
 7520    // Same thing, right-to-left
 7521    test(
 7522        &mut cx,
 7523        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7524        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7525    );
 7526
 7527    // Whole buffer, left-to-right, last line ends with newline
 7528    test(
 7529        &mut cx,
 7530        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7531        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7532    );
 7533    // Same thing, right-to-left
 7534    test(
 7535        &mut cx,
 7536        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7537        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7538    );
 7539
 7540    // Starts at the end of a line, ends at the start of another
 7541    test(
 7542        &mut cx,
 7543        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7544        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7545    );
 7546}
 7547
 7548#[gpui::test]
 7549async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7550    init_test(cx, |_| {});
 7551
 7552    let editor = cx.add_window(|window, cx| {
 7553        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7554        build_editor(buffer, window, cx)
 7555    });
 7556
 7557    // setup
 7558    _ = editor.update(cx, |editor, window, cx| {
 7559        editor.fold_creases(
 7560            vec![
 7561                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7562                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7563                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7564            ],
 7565            true,
 7566            window,
 7567            cx,
 7568        );
 7569        assert_eq!(
 7570            editor.display_text(cx),
 7571            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7572        );
 7573    });
 7574
 7575    _ = editor.update(cx, |editor, window, cx| {
 7576        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7577            s.select_display_ranges([
 7578                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7579                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7580                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7581                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7582            ])
 7583        });
 7584        editor.split_selection_into_lines(&Default::default(), window, cx);
 7585        assert_eq!(
 7586            editor.display_text(cx),
 7587            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7588        );
 7589    });
 7590    EditorTestContext::for_editor(editor, cx)
 7591        .await
 7592        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7593
 7594    _ = editor.update(cx, |editor, window, cx| {
 7595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7596            s.select_display_ranges([
 7597                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7598            ])
 7599        });
 7600        editor.split_selection_into_lines(&Default::default(), window, cx);
 7601        assert_eq!(
 7602            editor.display_text(cx),
 7603            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7604        );
 7605        assert_eq!(
 7606            editor.selections.display_ranges(cx),
 7607            [
 7608                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7609                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7610                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7611                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7612                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7613                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7614                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7615            ]
 7616        );
 7617    });
 7618    EditorTestContext::for_editor(editor, cx)
 7619        .await
 7620        .assert_editor_state(
 7621            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7622        );
 7623}
 7624
 7625#[gpui::test]
 7626async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7627    init_test(cx, |_| {});
 7628
 7629    let mut cx = EditorTestContext::new(cx).await;
 7630
 7631    cx.set_state(indoc!(
 7632        r#"abc
 7633           defˇghi
 7634
 7635           jk
 7636           nlmo
 7637           "#
 7638    ));
 7639
 7640    cx.update_editor(|editor, window, cx| {
 7641        editor.add_selection_above(&Default::default(), window, cx);
 7642    });
 7643
 7644    cx.assert_editor_state(indoc!(
 7645        r#"abcˇ
 7646           defˇghi
 7647
 7648           jk
 7649           nlmo
 7650           "#
 7651    ));
 7652
 7653    cx.update_editor(|editor, window, cx| {
 7654        editor.add_selection_above(&Default::default(), window, cx);
 7655    });
 7656
 7657    cx.assert_editor_state(indoc!(
 7658        r#"abcˇ
 7659            defˇghi
 7660
 7661            jk
 7662            nlmo
 7663            "#
 7664    ));
 7665
 7666    cx.update_editor(|editor, window, cx| {
 7667        editor.add_selection_below(&Default::default(), window, cx);
 7668    });
 7669
 7670    cx.assert_editor_state(indoc!(
 7671        r#"abc
 7672           defˇghi
 7673
 7674           jk
 7675           nlmo
 7676           "#
 7677    ));
 7678
 7679    cx.update_editor(|editor, window, cx| {
 7680        editor.undo_selection(&Default::default(), window, cx);
 7681    });
 7682
 7683    cx.assert_editor_state(indoc!(
 7684        r#"abcˇ
 7685           defˇghi
 7686
 7687           jk
 7688           nlmo
 7689           "#
 7690    ));
 7691
 7692    cx.update_editor(|editor, window, cx| {
 7693        editor.redo_selection(&Default::default(), window, cx);
 7694    });
 7695
 7696    cx.assert_editor_state(indoc!(
 7697        r#"abc
 7698           defˇghi
 7699
 7700           jk
 7701           nlmo
 7702           "#
 7703    ));
 7704
 7705    cx.update_editor(|editor, window, cx| {
 7706        editor.add_selection_below(&Default::default(), window, cx);
 7707    });
 7708
 7709    cx.assert_editor_state(indoc!(
 7710        r#"abc
 7711           defˇghi
 7712           ˇ
 7713           jk
 7714           nlmo
 7715           "#
 7716    ));
 7717
 7718    cx.update_editor(|editor, window, cx| {
 7719        editor.add_selection_below(&Default::default(), window, cx);
 7720    });
 7721
 7722    cx.assert_editor_state(indoc!(
 7723        r#"abc
 7724           defˇghi
 7725           ˇ
 7726           jkˇ
 7727           nlmo
 7728           "#
 7729    ));
 7730
 7731    cx.update_editor(|editor, window, cx| {
 7732        editor.add_selection_below(&Default::default(), window, cx);
 7733    });
 7734
 7735    cx.assert_editor_state(indoc!(
 7736        r#"abc
 7737           defˇghi
 7738           ˇ
 7739           jkˇ
 7740           nlmˇo
 7741           "#
 7742    ));
 7743
 7744    cx.update_editor(|editor, window, cx| {
 7745        editor.add_selection_below(&Default::default(), window, cx);
 7746    });
 7747
 7748    cx.assert_editor_state(indoc!(
 7749        r#"abc
 7750           defˇghi
 7751           ˇ
 7752           jkˇ
 7753           nlmˇo
 7754           ˇ"#
 7755    ));
 7756
 7757    // change selections
 7758    cx.set_state(indoc!(
 7759        r#"abc
 7760           def«ˇg»hi
 7761
 7762           jk
 7763           nlmo
 7764           "#
 7765    ));
 7766
 7767    cx.update_editor(|editor, window, cx| {
 7768        editor.add_selection_below(&Default::default(), window, cx);
 7769    });
 7770
 7771    cx.assert_editor_state(indoc!(
 7772        r#"abc
 7773           def«ˇg»hi
 7774
 7775           jk
 7776           nlm«ˇo»
 7777           "#
 7778    ));
 7779
 7780    cx.update_editor(|editor, window, cx| {
 7781        editor.add_selection_below(&Default::default(), window, cx);
 7782    });
 7783
 7784    cx.assert_editor_state(indoc!(
 7785        r#"abc
 7786           def«ˇg»hi
 7787
 7788           jk
 7789           nlm«ˇo»
 7790           "#
 7791    ));
 7792
 7793    cx.update_editor(|editor, window, cx| {
 7794        editor.add_selection_above(&Default::default(), window, cx);
 7795    });
 7796
 7797    cx.assert_editor_state(indoc!(
 7798        r#"abc
 7799           def«ˇg»hi
 7800
 7801           jk
 7802           nlmo
 7803           "#
 7804    ));
 7805
 7806    cx.update_editor(|editor, window, cx| {
 7807        editor.add_selection_above(&Default::default(), window, cx);
 7808    });
 7809
 7810    cx.assert_editor_state(indoc!(
 7811        r#"abc
 7812           def«ˇg»hi
 7813
 7814           jk
 7815           nlmo
 7816           "#
 7817    ));
 7818
 7819    // Change selections again
 7820    cx.set_state(indoc!(
 7821        r#"a«bc
 7822           defgˇ»hi
 7823
 7824           jk
 7825           nlmo
 7826           "#
 7827    ));
 7828
 7829    cx.update_editor(|editor, window, cx| {
 7830        editor.add_selection_below(&Default::default(), window, cx);
 7831    });
 7832
 7833    cx.assert_editor_state(indoc!(
 7834        r#"a«bcˇ»
 7835           d«efgˇ»hi
 7836
 7837           j«kˇ»
 7838           nlmo
 7839           "#
 7840    ));
 7841
 7842    cx.update_editor(|editor, window, cx| {
 7843        editor.add_selection_below(&Default::default(), window, cx);
 7844    });
 7845    cx.assert_editor_state(indoc!(
 7846        r#"a«bcˇ»
 7847           d«efgˇ»hi
 7848
 7849           j«kˇ»
 7850           n«lmoˇ»
 7851           "#
 7852    ));
 7853    cx.update_editor(|editor, window, cx| {
 7854        editor.add_selection_above(&Default::default(), window, cx);
 7855    });
 7856
 7857    cx.assert_editor_state(indoc!(
 7858        r#"a«bcˇ»
 7859           d«efgˇ»hi
 7860
 7861           j«kˇ»
 7862           nlmo
 7863           "#
 7864    ));
 7865
 7866    // Change selections again
 7867    cx.set_state(indoc!(
 7868        r#"abc
 7869           d«ˇefghi
 7870
 7871           jk
 7872           nlm»o
 7873           "#
 7874    ));
 7875
 7876    cx.update_editor(|editor, window, cx| {
 7877        editor.add_selection_above(&Default::default(), window, cx);
 7878    });
 7879
 7880    cx.assert_editor_state(indoc!(
 7881        r#"a«ˇbc»
 7882           d«ˇef»ghi
 7883
 7884           j«ˇk»
 7885           n«ˇlm»o
 7886           "#
 7887    ));
 7888
 7889    cx.update_editor(|editor, window, cx| {
 7890        editor.add_selection_below(&Default::default(), window, cx);
 7891    });
 7892
 7893    cx.assert_editor_state(indoc!(
 7894        r#"abc
 7895           d«ˇef»ghi
 7896
 7897           j«ˇk»
 7898           n«ˇlm»o
 7899           "#
 7900    ));
 7901}
 7902
 7903#[gpui::test]
 7904async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7905    init_test(cx, |_| {});
 7906    let mut cx = EditorTestContext::new(cx).await;
 7907
 7908    cx.set_state(indoc!(
 7909        r#"line onˇe
 7910           liˇne two
 7911           line three
 7912           line four"#
 7913    ));
 7914
 7915    cx.update_editor(|editor, window, cx| {
 7916        editor.add_selection_below(&Default::default(), window, cx);
 7917    });
 7918
 7919    // test multiple cursors expand in the same direction
 7920    cx.assert_editor_state(indoc!(
 7921        r#"line onˇe
 7922           liˇne twˇo
 7923           liˇne three
 7924           line four"#
 7925    ));
 7926
 7927    cx.update_editor(|editor, window, cx| {
 7928        editor.add_selection_below(&Default::default(), window, cx);
 7929    });
 7930
 7931    cx.update_editor(|editor, window, cx| {
 7932        editor.add_selection_below(&Default::default(), window, cx);
 7933    });
 7934
 7935    // test multiple cursors expand below overflow
 7936    cx.assert_editor_state(indoc!(
 7937        r#"line onˇe
 7938           liˇne twˇo
 7939           liˇne thˇree
 7940           liˇne foˇur"#
 7941    ));
 7942
 7943    cx.update_editor(|editor, window, cx| {
 7944        editor.add_selection_above(&Default::default(), window, cx);
 7945    });
 7946
 7947    // test multiple cursors retrieves back correctly
 7948    cx.assert_editor_state(indoc!(
 7949        r#"line onˇe
 7950           liˇne twˇo
 7951           liˇne thˇree
 7952           line four"#
 7953    ));
 7954
 7955    cx.update_editor(|editor, window, cx| {
 7956        editor.add_selection_above(&Default::default(), window, cx);
 7957    });
 7958
 7959    cx.update_editor(|editor, window, cx| {
 7960        editor.add_selection_above(&Default::default(), window, cx);
 7961    });
 7962
 7963    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7964    cx.assert_editor_state(indoc!(
 7965        r#"liˇne onˇe
 7966           liˇne two
 7967           line three
 7968           line four"#
 7969    ));
 7970
 7971    cx.update_editor(|editor, window, cx| {
 7972        editor.undo_selection(&Default::default(), window, cx);
 7973    });
 7974
 7975    // test undo
 7976    cx.assert_editor_state(indoc!(
 7977        r#"line onˇe
 7978           liˇne twˇo
 7979           line three
 7980           line four"#
 7981    ));
 7982
 7983    cx.update_editor(|editor, window, cx| {
 7984        editor.redo_selection(&Default::default(), window, cx);
 7985    });
 7986
 7987    // test redo
 7988    cx.assert_editor_state(indoc!(
 7989        r#"liˇne onˇe
 7990           liˇne two
 7991           line three
 7992           line four"#
 7993    ));
 7994
 7995    cx.set_state(indoc!(
 7996        r#"abcd
 7997           ef«ghˇ»
 7998           ijkl
 7999           «mˇ»nop"#
 8000    ));
 8001
 8002    cx.update_editor(|editor, window, cx| {
 8003        editor.add_selection_above(&Default::default(), window, cx);
 8004    });
 8005
 8006    // test multiple selections expand in the same direction
 8007    cx.assert_editor_state(indoc!(
 8008        r#"ab«cdˇ»
 8009           ef«ghˇ»
 8010           «iˇ»jkl
 8011           «mˇ»nop"#
 8012    ));
 8013
 8014    cx.update_editor(|editor, window, cx| {
 8015        editor.add_selection_above(&Default::default(), window, cx);
 8016    });
 8017
 8018    // test multiple selection upward overflow
 8019    cx.assert_editor_state(indoc!(
 8020        r#"ab«cdˇ»
 8021           «eˇ»f«ghˇ»
 8022           «iˇ»jkl
 8023           «mˇ»nop"#
 8024    ));
 8025
 8026    cx.update_editor(|editor, window, cx| {
 8027        editor.add_selection_below(&Default::default(), window, cx);
 8028    });
 8029
 8030    // test multiple selection retrieves back correctly
 8031    cx.assert_editor_state(indoc!(
 8032        r#"abcd
 8033           ef«ghˇ»
 8034           «iˇ»jkl
 8035           «mˇ»nop"#
 8036    ));
 8037
 8038    cx.update_editor(|editor, window, cx| {
 8039        editor.add_selection_below(&Default::default(), window, cx);
 8040    });
 8041
 8042    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8043    cx.assert_editor_state(indoc!(
 8044        r#"abcd
 8045           ef«ghˇ»
 8046           ij«klˇ»
 8047           «mˇ»nop"#
 8048    ));
 8049
 8050    cx.update_editor(|editor, window, cx| {
 8051        editor.undo_selection(&Default::default(), window, cx);
 8052    });
 8053
 8054    // test undo
 8055    cx.assert_editor_state(indoc!(
 8056        r#"abcd
 8057           ef«ghˇ»
 8058           «iˇ»jkl
 8059           «mˇ»nop"#
 8060    ));
 8061
 8062    cx.update_editor(|editor, window, cx| {
 8063        editor.redo_selection(&Default::default(), window, cx);
 8064    });
 8065
 8066    // test redo
 8067    cx.assert_editor_state(indoc!(
 8068        r#"abcd
 8069           ef«ghˇ»
 8070           ij«klˇ»
 8071           «mˇ»nop"#
 8072    ));
 8073}
 8074
 8075#[gpui::test]
 8076async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8077    init_test(cx, |_| {});
 8078    let mut cx = EditorTestContext::new(cx).await;
 8079
 8080    cx.set_state(indoc!(
 8081        r#"line onˇe
 8082           liˇne two
 8083           line three
 8084           line four"#
 8085    ));
 8086
 8087    cx.update_editor(|editor, window, cx| {
 8088        editor.add_selection_below(&Default::default(), window, cx);
 8089        editor.add_selection_below(&Default::default(), window, cx);
 8090        editor.add_selection_below(&Default::default(), window, cx);
 8091    });
 8092
 8093    // initial state with two multi cursor groups
 8094    cx.assert_editor_state(indoc!(
 8095        r#"line onˇe
 8096           liˇne twˇo
 8097           liˇne thˇree
 8098           liˇne foˇur"#
 8099    ));
 8100
 8101    // add single cursor in middle - simulate opt click
 8102    cx.update_editor(|editor, window, cx| {
 8103        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8104        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8105        editor.end_selection(window, cx);
 8106    });
 8107
 8108    cx.assert_editor_state(indoc!(
 8109        r#"line onˇe
 8110           liˇne twˇo
 8111           liˇneˇ thˇree
 8112           liˇne foˇur"#
 8113    ));
 8114
 8115    cx.update_editor(|editor, window, cx| {
 8116        editor.add_selection_above(&Default::default(), window, cx);
 8117    });
 8118
 8119    // test new added selection expands above and existing selection shrinks
 8120    cx.assert_editor_state(indoc!(
 8121        r#"line onˇe
 8122           liˇneˇ twˇo
 8123           liˇneˇ thˇree
 8124           line four"#
 8125    ));
 8126
 8127    cx.update_editor(|editor, window, cx| {
 8128        editor.add_selection_above(&Default::default(), window, cx);
 8129    });
 8130
 8131    // test new added selection expands above and existing selection shrinks
 8132    cx.assert_editor_state(indoc!(
 8133        r#"lineˇ onˇe
 8134           liˇneˇ twˇo
 8135           lineˇ three
 8136           line four"#
 8137    ));
 8138
 8139    // intial state with two selection groups
 8140    cx.set_state(indoc!(
 8141        r#"abcd
 8142           ef«ghˇ»
 8143           ijkl
 8144           «mˇ»nop"#
 8145    ));
 8146
 8147    cx.update_editor(|editor, window, cx| {
 8148        editor.add_selection_above(&Default::default(), window, cx);
 8149        editor.add_selection_above(&Default::default(), window, cx);
 8150    });
 8151
 8152    cx.assert_editor_state(indoc!(
 8153        r#"ab«cdˇ»
 8154           «eˇ»f«ghˇ»
 8155           «iˇ»jkl
 8156           «mˇ»nop"#
 8157    ));
 8158
 8159    // add single selection in middle - simulate opt drag
 8160    cx.update_editor(|editor, window, cx| {
 8161        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8162        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8163        editor.update_selection(
 8164            DisplayPoint::new(DisplayRow(2), 4),
 8165            0,
 8166            gpui::Point::<f32>::default(),
 8167            window,
 8168            cx,
 8169        );
 8170        editor.end_selection(window, cx);
 8171    });
 8172
 8173    cx.assert_editor_state(indoc!(
 8174        r#"ab«cdˇ»
 8175           «eˇ»f«ghˇ»
 8176           «iˇ»jk«lˇ»
 8177           «mˇ»nop"#
 8178    ));
 8179
 8180    cx.update_editor(|editor, window, cx| {
 8181        editor.add_selection_below(&Default::default(), window, cx);
 8182    });
 8183
 8184    // test new added selection expands below, others shrinks from above
 8185    cx.assert_editor_state(indoc!(
 8186        r#"abcd
 8187           ef«ghˇ»
 8188           «iˇ»jk«lˇ»
 8189           «mˇ»no«pˇ»"#
 8190    ));
 8191}
 8192
 8193#[gpui::test]
 8194async fn test_select_next(cx: &mut TestAppContext) {
 8195    init_test(cx, |_| {});
 8196
 8197    let mut cx = EditorTestContext::new(cx).await;
 8198    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8199
 8200    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8201        .unwrap();
 8202    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8203
 8204    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8205        .unwrap();
 8206    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8207
 8208    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8209    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8210
 8211    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8212    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8213
 8214    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8215        .unwrap();
 8216    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8217
 8218    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8219        .unwrap();
 8220    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8221
 8222    // Test selection direction should be preserved
 8223    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8224
 8225    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8226        .unwrap();
 8227    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8228}
 8229
 8230#[gpui::test]
 8231async fn test_select_all_matches(cx: &mut TestAppContext) {
 8232    init_test(cx, |_| {});
 8233
 8234    let mut cx = EditorTestContext::new(cx).await;
 8235
 8236    // Test caret-only selections
 8237    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8238    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8239        .unwrap();
 8240    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8241
 8242    // Test left-to-right selections
 8243    cx.set_state("abc\n«abcˇ»\nabc");
 8244    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8245        .unwrap();
 8246    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8247
 8248    // Test right-to-left selections
 8249    cx.set_state("abc\n«ˇabc»\nabc");
 8250    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8251        .unwrap();
 8252    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8253
 8254    // Test selecting whitespace with caret selection
 8255    cx.set_state("abc\nˇ   abc\nabc");
 8256    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8257        .unwrap();
 8258    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8259
 8260    // Test selecting whitespace with left-to-right selection
 8261    cx.set_state("abc\n«ˇ  »abc\nabc");
 8262    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8263        .unwrap();
 8264    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8265
 8266    // Test no matches with right-to-left selection
 8267    cx.set_state("abc\n«  ˇ»abc\nabc");
 8268    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8269        .unwrap();
 8270    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8271
 8272    // Test with a single word and clip_at_line_ends=true (#29823)
 8273    cx.set_state("aˇbc");
 8274    cx.update_editor(|e, window, cx| {
 8275        e.set_clip_at_line_ends(true, cx);
 8276        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8277        e.set_clip_at_line_ends(false, cx);
 8278    });
 8279    cx.assert_editor_state("«abcˇ»");
 8280}
 8281
 8282#[gpui::test]
 8283async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8284    init_test(cx, |_| {});
 8285
 8286    let mut cx = EditorTestContext::new(cx).await;
 8287
 8288    let large_body_1 = "\nd".repeat(200);
 8289    let large_body_2 = "\ne".repeat(200);
 8290
 8291    cx.set_state(&format!(
 8292        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8293    ));
 8294    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8295        let scroll_position = editor.scroll_position(cx);
 8296        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8297        scroll_position
 8298    });
 8299
 8300    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8301        .unwrap();
 8302    cx.assert_editor_state(&format!(
 8303        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8304    ));
 8305    let scroll_position_after_selection =
 8306        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8307    assert_eq!(
 8308        initial_scroll_position, scroll_position_after_selection,
 8309        "Scroll position should not change after selecting all matches"
 8310    );
 8311}
 8312
 8313#[gpui::test]
 8314async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8315    init_test(cx, |_| {});
 8316
 8317    let mut cx = EditorLspTestContext::new_rust(
 8318        lsp::ServerCapabilities {
 8319            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8320            ..Default::default()
 8321        },
 8322        cx,
 8323    )
 8324    .await;
 8325
 8326    cx.set_state(indoc! {"
 8327        line 1
 8328        line 2
 8329        linˇe 3
 8330        line 4
 8331        line 5
 8332    "});
 8333
 8334    // Make an edit
 8335    cx.update_editor(|editor, window, cx| {
 8336        editor.handle_input("X", window, cx);
 8337    });
 8338
 8339    // Move cursor to a different position
 8340    cx.update_editor(|editor, window, cx| {
 8341        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8342            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8343        });
 8344    });
 8345
 8346    cx.assert_editor_state(indoc! {"
 8347        line 1
 8348        line 2
 8349        linXe 3
 8350        line 4
 8351        liˇne 5
 8352    "});
 8353
 8354    cx.lsp
 8355        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8356            Ok(Some(vec![lsp::TextEdit::new(
 8357                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8358                "PREFIX ".to_string(),
 8359            )]))
 8360        });
 8361
 8362    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8363        .unwrap()
 8364        .await
 8365        .unwrap();
 8366
 8367    cx.assert_editor_state(indoc! {"
 8368        PREFIX line 1
 8369        line 2
 8370        linXe 3
 8371        line 4
 8372        liˇne 5
 8373    "});
 8374
 8375    // Undo formatting
 8376    cx.update_editor(|editor, window, cx| {
 8377        editor.undo(&Default::default(), window, cx);
 8378    });
 8379
 8380    // Verify cursor moved back to position after edit
 8381    cx.assert_editor_state(indoc! {"
 8382        line 1
 8383        line 2
 8384        linXˇe 3
 8385        line 4
 8386        line 5
 8387    "});
 8388}
 8389
 8390#[gpui::test]
 8391async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8392    init_test(cx, |_| {});
 8393
 8394    let mut cx = EditorTestContext::new(cx).await;
 8395
 8396    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8397    cx.update_editor(|editor, window, cx| {
 8398        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8399    });
 8400
 8401    cx.set_state(indoc! {"
 8402        line 1
 8403        line 2
 8404        linˇe 3
 8405        line 4
 8406        line 5
 8407        line 6
 8408        line 7
 8409        line 8
 8410        line 9
 8411        line 10
 8412    "});
 8413
 8414    let snapshot = cx.buffer_snapshot();
 8415    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8416
 8417    cx.update(|_, cx| {
 8418        provider.update(cx, |provider, _| {
 8419            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8420                id: None,
 8421                edits: vec![(edit_position..edit_position, "X".into())],
 8422                edit_preview: None,
 8423            }))
 8424        })
 8425    });
 8426
 8427    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8428    cx.update_editor(|editor, window, cx| {
 8429        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8430    });
 8431
 8432    cx.assert_editor_state(indoc! {"
 8433        line 1
 8434        line 2
 8435        lineXˇ 3
 8436        line 4
 8437        line 5
 8438        line 6
 8439        line 7
 8440        line 8
 8441        line 9
 8442        line 10
 8443    "});
 8444
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8447            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8448        });
 8449    });
 8450
 8451    cx.assert_editor_state(indoc! {"
 8452        line 1
 8453        line 2
 8454        lineX 3
 8455        line 4
 8456        line 5
 8457        line 6
 8458        line 7
 8459        line 8
 8460        line 9
 8461        liˇne 10
 8462    "});
 8463
 8464    cx.update_editor(|editor, window, cx| {
 8465        editor.undo(&Default::default(), window, cx);
 8466    });
 8467
 8468    cx.assert_editor_state(indoc! {"
 8469        line 1
 8470        line 2
 8471        lineˇ 3
 8472        line 4
 8473        line 5
 8474        line 6
 8475        line 7
 8476        line 8
 8477        line 9
 8478        line 10
 8479    "});
 8480}
 8481
 8482#[gpui::test]
 8483async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8484    init_test(cx, |_| {});
 8485
 8486    let mut cx = EditorTestContext::new(cx).await;
 8487    cx.set_state(
 8488        r#"let foo = 2;
 8489lˇet foo = 2;
 8490let fooˇ = 2;
 8491let foo = 2;
 8492let foo = ˇ2;"#,
 8493    );
 8494
 8495    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8496        .unwrap();
 8497    cx.assert_editor_state(
 8498        r#"let foo = 2;
 8499«letˇ» foo = 2;
 8500let «fooˇ» = 2;
 8501let foo = 2;
 8502let foo = «2ˇ»;"#,
 8503    );
 8504
 8505    // noop for multiple selections with different contents
 8506    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8507        .unwrap();
 8508    cx.assert_editor_state(
 8509        r#"let foo = 2;
 8510«letˇ» foo = 2;
 8511let «fooˇ» = 2;
 8512let foo = 2;
 8513let foo = «2ˇ»;"#,
 8514    );
 8515
 8516    // Test last selection direction should be preserved
 8517    cx.set_state(
 8518        r#"let foo = 2;
 8519let foo = 2;
 8520let «fooˇ» = 2;
 8521let «ˇfoo» = 2;
 8522let foo = 2;"#,
 8523    );
 8524
 8525    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8526        .unwrap();
 8527    cx.assert_editor_state(
 8528        r#"let foo = 2;
 8529let foo = 2;
 8530let «fooˇ» = 2;
 8531let «ˇfoo» = 2;
 8532let «ˇfoo» = 2;"#,
 8533    );
 8534}
 8535
 8536#[gpui::test]
 8537async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8538    init_test(cx, |_| {});
 8539
 8540    let mut cx =
 8541        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8542
 8543    cx.assert_editor_state(indoc! {"
 8544        ˇbbb
 8545        ccc
 8546
 8547        bbb
 8548        ccc
 8549        "});
 8550    cx.dispatch_action(SelectPrevious::default());
 8551    cx.assert_editor_state(indoc! {"
 8552                «bbbˇ»
 8553                ccc
 8554
 8555                bbb
 8556                ccc
 8557                "});
 8558    cx.dispatch_action(SelectPrevious::default());
 8559    cx.assert_editor_state(indoc! {"
 8560                «bbbˇ»
 8561                ccc
 8562
 8563                «bbbˇ»
 8564                ccc
 8565                "});
 8566}
 8567
 8568#[gpui::test]
 8569async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8570    init_test(cx, |_| {});
 8571
 8572    let mut cx = EditorTestContext::new(cx).await;
 8573    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8574
 8575    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8576        .unwrap();
 8577    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8578
 8579    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8580        .unwrap();
 8581    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8582
 8583    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8584    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8585
 8586    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8587    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8588
 8589    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8590        .unwrap();
 8591    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8592
 8593    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8594        .unwrap();
 8595    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8596}
 8597
 8598#[gpui::test]
 8599async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8600    init_test(cx, |_| {});
 8601
 8602    let mut cx = EditorTestContext::new(cx).await;
 8603    cx.set_state("");
 8604
 8605    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8606        .unwrap();
 8607    cx.assert_editor_state("«aˇ»");
 8608    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8609        .unwrap();
 8610    cx.assert_editor_state("«aˇ»");
 8611}
 8612
 8613#[gpui::test]
 8614async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8615    init_test(cx, |_| {});
 8616
 8617    let mut cx = EditorTestContext::new(cx).await;
 8618    cx.set_state(
 8619        r#"let foo = 2;
 8620lˇet foo = 2;
 8621let fooˇ = 2;
 8622let foo = 2;
 8623let foo = ˇ2;"#,
 8624    );
 8625
 8626    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8627        .unwrap();
 8628    cx.assert_editor_state(
 8629        r#"let foo = 2;
 8630«letˇ» foo = 2;
 8631let «fooˇ» = 2;
 8632let foo = 2;
 8633let foo = «2ˇ»;"#,
 8634    );
 8635
 8636    // noop for multiple selections with different contents
 8637    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8638        .unwrap();
 8639    cx.assert_editor_state(
 8640        r#"let foo = 2;
 8641«letˇ» foo = 2;
 8642let «fooˇ» = 2;
 8643let foo = 2;
 8644let foo = «2ˇ»;"#,
 8645    );
 8646}
 8647
 8648#[gpui::test]
 8649async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8650    init_test(cx, |_| {});
 8651
 8652    let mut cx = EditorTestContext::new(cx).await;
 8653    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8654
 8655    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8656        .unwrap();
 8657    // selection direction is preserved
 8658    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8659
 8660    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8661        .unwrap();
 8662    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8663
 8664    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8665    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8666
 8667    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8668    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8669
 8670    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8671        .unwrap();
 8672    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8673
 8674    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8675        .unwrap();
 8676    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8677}
 8678
 8679#[gpui::test]
 8680async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8681    init_test(cx, |_| {});
 8682
 8683    let language = Arc::new(Language::new(
 8684        LanguageConfig::default(),
 8685        Some(tree_sitter_rust::LANGUAGE.into()),
 8686    ));
 8687
 8688    let text = r#"
 8689        use mod1::mod2::{mod3, mod4};
 8690
 8691        fn fn_1(param1: bool, param2: &str) {
 8692            let var1 = "text";
 8693        }
 8694    "#
 8695    .unindent();
 8696
 8697    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8698    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8699    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8700
 8701    editor
 8702        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8703        .await;
 8704
 8705    editor.update_in(cx, |editor, window, cx| {
 8706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8707            s.select_display_ranges([
 8708                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8709                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8710                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8711            ]);
 8712        });
 8713        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8714    });
 8715    editor.update(cx, |editor, cx| {
 8716        assert_text_with_selections(
 8717            editor,
 8718            indoc! {r#"
 8719                use mod1::mod2::{mod3, «mod4ˇ»};
 8720
 8721                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8722                    let var1 = "«ˇtext»";
 8723                }
 8724            "#},
 8725            cx,
 8726        );
 8727    });
 8728
 8729    editor.update_in(cx, |editor, window, cx| {
 8730        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8731    });
 8732    editor.update(cx, |editor, cx| {
 8733        assert_text_with_selections(
 8734            editor,
 8735            indoc! {r#"
 8736                use mod1::mod2::«{mod3, mod4}ˇ»;
 8737
 8738                «ˇfn fn_1(param1: bool, param2: &str) {
 8739                    let var1 = "text";
 8740 8741            "#},
 8742            cx,
 8743        );
 8744    });
 8745
 8746    editor.update_in(cx, |editor, window, cx| {
 8747        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8748    });
 8749    assert_eq!(
 8750        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8751        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8752    );
 8753
 8754    // Trying to expand the selected syntax node one more time has no effect.
 8755    editor.update_in(cx, |editor, window, cx| {
 8756        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8757    });
 8758    assert_eq!(
 8759        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8760        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8761    );
 8762
 8763    editor.update_in(cx, |editor, window, cx| {
 8764        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8765    });
 8766    editor.update(cx, |editor, cx| {
 8767        assert_text_with_selections(
 8768            editor,
 8769            indoc! {r#"
 8770                use mod1::mod2::«{mod3, mod4}ˇ»;
 8771
 8772                «ˇfn fn_1(param1: bool, param2: &str) {
 8773                    let var1 = "text";
 8774 8775            "#},
 8776            cx,
 8777        );
 8778    });
 8779
 8780    editor.update_in(cx, |editor, window, cx| {
 8781        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8782    });
 8783    editor.update(cx, |editor, cx| {
 8784        assert_text_with_selections(
 8785            editor,
 8786            indoc! {r#"
 8787                use mod1::mod2::{mod3, «mod4ˇ»};
 8788
 8789                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8790                    let var1 = "«ˇtext»";
 8791                }
 8792            "#},
 8793            cx,
 8794        );
 8795    });
 8796
 8797    editor.update_in(cx, |editor, window, cx| {
 8798        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8799    });
 8800    editor.update(cx, |editor, cx| {
 8801        assert_text_with_selections(
 8802            editor,
 8803            indoc! {r#"
 8804                use mod1::mod2::{mod3, moˇd4};
 8805
 8806                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8807                    let var1 = "teˇxt";
 8808                }
 8809            "#},
 8810            cx,
 8811        );
 8812    });
 8813
 8814    // Trying to shrink the selected syntax node one more time has no effect.
 8815    editor.update_in(cx, |editor, window, cx| {
 8816        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8817    });
 8818    editor.update_in(cx, |editor, _, cx| {
 8819        assert_text_with_selections(
 8820            editor,
 8821            indoc! {r#"
 8822                use mod1::mod2::{mod3, moˇd4};
 8823
 8824                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8825                    let var1 = "teˇxt";
 8826                }
 8827            "#},
 8828            cx,
 8829        );
 8830    });
 8831
 8832    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8833    // a fold.
 8834    editor.update_in(cx, |editor, window, cx| {
 8835        editor.fold_creases(
 8836            vec![
 8837                Crease::simple(
 8838                    Point::new(0, 21)..Point::new(0, 24),
 8839                    FoldPlaceholder::test(),
 8840                ),
 8841                Crease::simple(
 8842                    Point::new(3, 20)..Point::new(3, 22),
 8843                    FoldPlaceholder::test(),
 8844                ),
 8845            ],
 8846            true,
 8847            window,
 8848            cx,
 8849        );
 8850        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8851    });
 8852    editor.update(cx, |editor, cx| {
 8853        assert_text_with_selections(
 8854            editor,
 8855            indoc! {r#"
 8856                use mod1::mod2::«{mod3, mod4}ˇ»;
 8857
 8858                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8859                    let var1 = "«ˇtext»";
 8860                }
 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865}
 8866
 8867#[gpui::test]
 8868async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8869    init_test(cx, |_| {});
 8870
 8871    let language = Arc::new(Language::new(
 8872        LanguageConfig::default(),
 8873        Some(tree_sitter_rust::LANGUAGE.into()),
 8874    ));
 8875
 8876    let text = "let a = 2;";
 8877
 8878    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8879    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8880    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8881
 8882    editor
 8883        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8884        .await;
 8885
 8886    // Test case 1: Cursor at end of word
 8887    editor.update_in(cx, |editor, window, cx| {
 8888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8889            s.select_display_ranges([
 8890                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8891            ]);
 8892        });
 8893    });
 8894    editor.update(cx, |editor, cx| {
 8895        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8896    });
 8897    editor.update_in(cx, |editor, window, cx| {
 8898        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8899    });
 8900    editor.update(cx, |editor, cx| {
 8901        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8902    });
 8903    editor.update_in(cx, |editor, window, cx| {
 8904        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8905    });
 8906    editor.update(cx, |editor, cx| {
 8907        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8908    });
 8909
 8910    // Test case 2: Cursor at end of statement
 8911    editor.update_in(cx, |editor, window, cx| {
 8912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8913            s.select_display_ranges([
 8914                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8915            ]);
 8916        });
 8917    });
 8918    editor.update(cx, |editor, cx| {
 8919        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8920    });
 8921    editor.update_in(cx, |editor, window, cx| {
 8922        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8923    });
 8924    editor.update(cx, |editor, cx| {
 8925        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8926    });
 8927}
 8928
 8929#[gpui::test]
 8930async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8931    init_test(cx, |_| {});
 8932
 8933    let language = Arc::new(Language::new(
 8934        LanguageConfig {
 8935            name: "JavaScript".into(),
 8936            ..Default::default()
 8937        },
 8938        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8939    ));
 8940
 8941    let text = r#"
 8942        let a = {
 8943            key: "value",
 8944        };
 8945    "#
 8946    .unindent();
 8947
 8948    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8949    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8950    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8951
 8952    editor
 8953        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8954        .await;
 8955
 8956    // Test case 1: Cursor after '{'
 8957    editor.update_in(cx, |editor, window, cx| {
 8958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8959            s.select_display_ranges([
 8960                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8961            ]);
 8962        });
 8963    });
 8964    editor.update(cx, |editor, cx| {
 8965        assert_text_with_selections(
 8966            editor,
 8967            indoc! {r#"
 8968                let a = {ˇ
 8969                    key: "value",
 8970                };
 8971            "#},
 8972            cx,
 8973        );
 8974    });
 8975    editor.update_in(cx, |editor, window, cx| {
 8976        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8977    });
 8978    editor.update(cx, |editor, cx| {
 8979        assert_text_with_selections(
 8980            editor,
 8981            indoc! {r#"
 8982                let a = «ˇ{
 8983                    key: "value",
 8984                }»;
 8985            "#},
 8986            cx,
 8987        );
 8988    });
 8989
 8990    // Test case 2: Cursor after ':'
 8991    editor.update_in(cx, |editor, window, cx| {
 8992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8993            s.select_display_ranges([
 8994                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8995            ]);
 8996        });
 8997    });
 8998    editor.update(cx, |editor, cx| {
 8999        assert_text_with_selections(
 9000            editor,
 9001            indoc! {r#"
 9002                let a = {
 9003                    key:ˇ "value",
 9004                };
 9005            "#},
 9006            cx,
 9007        );
 9008    });
 9009    editor.update_in(cx, |editor, window, cx| {
 9010        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9011    });
 9012    editor.update(cx, |editor, cx| {
 9013        assert_text_with_selections(
 9014            editor,
 9015            indoc! {r#"
 9016                let a = {
 9017                    «ˇkey: "value"»,
 9018                };
 9019            "#},
 9020            cx,
 9021        );
 9022    });
 9023    editor.update_in(cx, |editor, window, cx| {
 9024        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9025    });
 9026    editor.update(cx, |editor, cx| {
 9027        assert_text_with_selections(
 9028            editor,
 9029            indoc! {r#"
 9030                let a = «ˇ{
 9031                    key: "value",
 9032                }»;
 9033            "#},
 9034            cx,
 9035        );
 9036    });
 9037
 9038    // Test case 3: Cursor after ','
 9039    editor.update_in(cx, |editor, window, cx| {
 9040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9041            s.select_display_ranges([
 9042                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9043            ]);
 9044        });
 9045    });
 9046    editor.update(cx, |editor, cx| {
 9047        assert_text_with_selections(
 9048            editor,
 9049            indoc! {r#"
 9050                let a = {
 9051                    key: "value",ˇ
 9052                };
 9053            "#},
 9054            cx,
 9055        );
 9056    });
 9057    editor.update_in(cx, |editor, window, cx| {
 9058        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9059    });
 9060    editor.update(cx, |editor, cx| {
 9061        assert_text_with_selections(
 9062            editor,
 9063            indoc! {r#"
 9064                let a = «ˇ{
 9065                    key: "value",
 9066                }»;
 9067            "#},
 9068            cx,
 9069        );
 9070    });
 9071
 9072    // Test case 4: Cursor after ';'
 9073    editor.update_in(cx, |editor, window, cx| {
 9074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9075            s.select_display_ranges([
 9076                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9077            ]);
 9078        });
 9079    });
 9080    editor.update(cx, |editor, cx| {
 9081        assert_text_with_selections(
 9082            editor,
 9083            indoc! {r#"
 9084                let a = {
 9085                    key: "value",
 9086                };ˇ
 9087            "#},
 9088            cx,
 9089        );
 9090    });
 9091    editor.update_in(cx, |editor, window, cx| {
 9092        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9093    });
 9094    editor.update(cx, |editor, cx| {
 9095        assert_text_with_selections(
 9096            editor,
 9097            indoc! {r#"
 9098                «ˇlet a = {
 9099                    key: "value",
 9100                };
 9101                »"#},
 9102            cx,
 9103        );
 9104    });
 9105}
 9106
 9107#[gpui::test]
 9108async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9109    init_test(cx, |_| {});
 9110
 9111    let language = Arc::new(Language::new(
 9112        LanguageConfig::default(),
 9113        Some(tree_sitter_rust::LANGUAGE.into()),
 9114    ));
 9115
 9116    let text = r#"
 9117        use mod1::mod2::{mod3, mod4};
 9118
 9119        fn fn_1(param1: bool, param2: &str) {
 9120            let var1 = "hello world";
 9121        }
 9122    "#
 9123    .unindent();
 9124
 9125    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9126    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9127    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9128
 9129    editor
 9130        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9131        .await;
 9132
 9133    // Test 1: Cursor on a letter of a string word
 9134    editor.update_in(cx, |editor, window, cx| {
 9135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9136            s.select_display_ranges([
 9137                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9138            ]);
 9139        });
 9140    });
 9141    editor.update_in(cx, |editor, window, cx| {
 9142        assert_text_with_selections(
 9143            editor,
 9144            indoc! {r#"
 9145                use mod1::mod2::{mod3, mod4};
 9146
 9147                fn fn_1(param1: bool, param2: &str) {
 9148                    let var1 = "hˇello world";
 9149                }
 9150            "#},
 9151            cx,
 9152        );
 9153        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9154        assert_text_with_selections(
 9155            editor,
 9156            indoc! {r#"
 9157                use mod1::mod2::{mod3, mod4};
 9158
 9159                fn fn_1(param1: bool, param2: &str) {
 9160                    let var1 = "«ˇhello» world";
 9161                }
 9162            "#},
 9163            cx,
 9164        );
 9165    });
 9166
 9167    // Test 2: Partial selection within a word
 9168    editor.update_in(cx, |editor, window, cx| {
 9169        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9170            s.select_display_ranges([
 9171                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9172            ]);
 9173        });
 9174    });
 9175    editor.update_in(cx, |editor, window, cx| {
 9176        assert_text_with_selections(
 9177            editor,
 9178            indoc! {r#"
 9179                use mod1::mod2::{mod3, mod4};
 9180
 9181                fn fn_1(param1: bool, param2: &str) {
 9182                    let var1 = "h«elˇ»lo world";
 9183                }
 9184            "#},
 9185            cx,
 9186        );
 9187        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9188        assert_text_with_selections(
 9189            editor,
 9190            indoc! {r#"
 9191                use mod1::mod2::{mod3, mod4};
 9192
 9193                fn fn_1(param1: bool, param2: &str) {
 9194                    let var1 = "«ˇhello» world";
 9195                }
 9196            "#},
 9197            cx,
 9198        );
 9199    });
 9200
 9201    // Test 3: Complete word already selected
 9202    editor.update_in(cx, |editor, window, cx| {
 9203        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9204            s.select_display_ranges([
 9205                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9206            ]);
 9207        });
 9208    });
 9209    editor.update_in(cx, |editor, window, cx| {
 9210        assert_text_with_selections(
 9211            editor,
 9212            indoc! {r#"
 9213                use mod1::mod2::{mod3, mod4};
 9214
 9215                fn fn_1(param1: bool, param2: &str) {
 9216                    let var1 = "«helloˇ» world";
 9217                }
 9218            "#},
 9219            cx,
 9220        );
 9221        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9222        assert_text_with_selections(
 9223            editor,
 9224            indoc! {r#"
 9225                use mod1::mod2::{mod3, mod4};
 9226
 9227                fn fn_1(param1: bool, param2: &str) {
 9228                    let var1 = "«hello worldˇ»";
 9229                }
 9230            "#},
 9231            cx,
 9232        );
 9233    });
 9234
 9235    // Test 4: Selection spanning across words
 9236    editor.update_in(cx, |editor, window, cx| {
 9237        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9238            s.select_display_ranges([
 9239                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9240            ]);
 9241        });
 9242    });
 9243    editor.update_in(cx, |editor, window, cx| {
 9244        assert_text_with_selections(
 9245            editor,
 9246            indoc! {r#"
 9247                use mod1::mod2::{mod3, mod4};
 9248
 9249                fn fn_1(param1: bool, param2: &str) {
 9250                    let var1 = "hel«lo woˇ»rld";
 9251                }
 9252            "#},
 9253            cx,
 9254        );
 9255        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9256        assert_text_with_selections(
 9257            editor,
 9258            indoc! {r#"
 9259                use mod1::mod2::{mod3, mod4};
 9260
 9261                fn fn_1(param1: bool, param2: &str) {
 9262                    let var1 = "«ˇhello world»";
 9263                }
 9264            "#},
 9265            cx,
 9266        );
 9267    });
 9268
 9269    // Test 5: Expansion beyond string
 9270    editor.update_in(cx, |editor, window, cx| {
 9271        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9272        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9273        assert_text_with_selections(
 9274            editor,
 9275            indoc! {r#"
 9276                use mod1::mod2::{mod3, mod4};
 9277
 9278                fn fn_1(param1: bool, param2: &str) {
 9279                    «ˇlet var1 = "hello world";»
 9280                }
 9281            "#},
 9282            cx,
 9283        );
 9284    });
 9285}
 9286
 9287#[gpui::test]
 9288async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9289    init_test(cx, |_| {});
 9290
 9291    let mut cx = EditorTestContext::new(cx).await;
 9292
 9293    let language = Arc::new(Language::new(
 9294        LanguageConfig::default(),
 9295        Some(tree_sitter_rust::LANGUAGE.into()),
 9296    ));
 9297
 9298    cx.update_buffer(|buffer, cx| {
 9299        buffer.set_language(Some(language), cx);
 9300    });
 9301
 9302    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9303    cx.update_editor(|editor, window, cx| {
 9304        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9305    });
 9306
 9307    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9308
 9309    cx.set_state(indoc! { r#"fn a() {
 9310          // what
 9311          // a
 9312          // ˇlong
 9313          // method
 9314          // I
 9315          // sure
 9316          // hope
 9317          // it
 9318          // works
 9319    }"# });
 9320
 9321    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9322    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9323    cx.update(|_, cx| {
 9324        multi_buffer.update(cx, |multi_buffer, cx| {
 9325            multi_buffer.set_excerpts_for_path(
 9326                PathKey::for_buffer(&buffer, cx),
 9327                buffer,
 9328                [Point::new(1, 0)..Point::new(1, 0)],
 9329                3,
 9330                cx,
 9331            );
 9332        });
 9333    });
 9334
 9335    let editor2 = cx.new_window_entity(|window, cx| {
 9336        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9337    });
 9338
 9339    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9340    cx.update_editor(|editor, window, cx| {
 9341        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9342            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9343        })
 9344    });
 9345
 9346    cx.assert_editor_state(indoc! { "
 9347        fn a() {
 9348              // what
 9349              // a
 9350        ˇ      // long
 9351              // method"});
 9352
 9353    cx.update_editor(|editor, window, cx| {
 9354        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9355    });
 9356
 9357    // Although we could potentially make the action work when the syntax node
 9358    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9359    // did. Maybe we could also expand the excerpt to contain the range?
 9360    cx.assert_editor_state(indoc! { "
 9361        fn a() {
 9362              // what
 9363              // a
 9364        ˇ      // long
 9365              // method"});
 9366}
 9367
 9368#[gpui::test]
 9369async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9370    init_test(cx, |_| {});
 9371
 9372    let base_text = r#"
 9373        impl A {
 9374            // this is an uncommitted comment
 9375
 9376            fn b() {
 9377                c();
 9378            }
 9379
 9380            // this is another uncommitted comment
 9381
 9382            fn d() {
 9383                // e
 9384                // f
 9385            }
 9386        }
 9387
 9388        fn g() {
 9389            // h
 9390        }
 9391    "#
 9392    .unindent();
 9393
 9394    let text = r#"
 9395        ˇimpl A {
 9396
 9397            fn b() {
 9398                c();
 9399            }
 9400
 9401            fn d() {
 9402                // e
 9403                // f
 9404            }
 9405        }
 9406
 9407        fn g() {
 9408            // h
 9409        }
 9410    "#
 9411    .unindent();
 9412
 9413    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9414    cx.set_state(&text);
 9415    cx.set_head_text(&base_text);
 9416    cx.update_editor(|editor, window, cx| {
 9417        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9418    });
 9419
 9420    cx.assert_state_with_diff(
 9421        "
 9422        ˇimpl A {
 9423      -     // this is an uncommitted comment
 9424
 9425            fn b() {
 9426                c();
 9427            }
 9428
 9429      -     // this is another uncommitted comment
 9430      -
 9431            fn d() {
 9432                // e
 9433                // f
 9434            }
 9435        }
 9436
 9437        fn g() {
 9438            // h
 9439        }
 9440    "
 9441        .unindent(),
 9442    );
 9443
 9444    let expected_display_text = "
 9445        impl A {
 9446            // this is an uncommitted comment
 9447
 9448            fn b() {
 9449 9450            }
 9451
 9452            // this is another uncommitted comment
 9453
 9454            fn d() {
 9455 9456            }
 9457        }
 9458
 9459        fn g() {
 9460 9461        }
 9462        "
 9463    .unindent();
 9464
 9465    cx.update_editor(|editor, window, cx| {
 9466        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9467        assert_eq!(editor.display_text(cx), expected_display_text);
 9468    });
 9469}
 9470
 9471#[gpui::test]
 9472async fn test_autoindent(cx: &mut TestAppContext) {
 9473    init_test(cx, |_| {});
 9474
 9475    let language = Arc::new(
 9476        Language::new(
 9477            LanguageConfig {
 9478                brackets: BracketPairConfig {
 9479                    pairs: vec![
 9480                        BracketPair {
 9481                            start: "{".to_string(),
 9482                            end: "}".to_string(),
 9483                            close: false,
 9484                            surround: false,
 9485                            newline: true,
 9486                        },
 9487                        BracketPair {
 9488                            start: "(".to_string(),
 9489                            end: ")".to_string(),
 9490                            close: false,
 9491                            surround: false,
 9492                            newline: true,
 9493                        },
 9494                    ],
 9495                    ..Default::default()
 9496                },
 9497                ..Default::default()
 9498            },
 9499            Some(tree_sitter_rust::LANGUAGE.into()),
 9500        )
 9501        .with_indents_query(
 9502            r#"
 9503                (_ "(" ")" @end) @indent
 9504                (_ "{" "}" @end) @indent
 9505            "#,
 9506        )
 9507        .unwrap(),
 9508    );
 9509
 9510    let text = "fn a() {}";
 9511
 9512    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9513    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9514    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9515    editor
 9516        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9517        .await;
 9518
 9519    editor.update_in(cx, |editor, window, cx| {
 9520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9521            s.select_ranges([5..5, 8..8, 9..9])
 9522        });
 9523        editor.newline(&Newline, window, cx);
 9524        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9525        assert_eq!(
 9526            editor.selections.ranges(cx),
 9527            &[
 9528                Point::new(1, 4)..Point::new(1, 4),
 9529                Point::new(3, 4)..Point::new(3, 4),
 9530                Point::new(5, 0)..Point::new(5, 0)
 9531            ]
 9532        );
 9533    });
 9534}
 9535
 9536#[gpui::test]
 9537async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9538    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9539
 9540    let language = Arc::new(
 9541        Language::new(
 9542            LanguageConfig {
 9543                brackets: BracketPairConfig {
 9544                    pairs: vec![
 9545                        BracketPair {
 9546                            start: "{".to_string(),
 9547                            end: "}".to_string(),
 9548                            close: false,
 9549                            surround: false,
 9550                            newline: true,
 9551                        },
 9552                        BracketPair {
 9553                            start: "(".to_string(),
 9554                            end: ")".to_string(),
 9555                            close: false,
 9556                            surround: false,
 9557                            newline: true,
 9558                        },
 9559                    ],
 9560                    ..Default::default()
 9561                },
 9562                ..Default::default()
 9563            },
 9564            Some(tree_sitter_rust::LANGUAGE.into()),
 9565        )
 9566        .with_indents_query(
 9567            r#"
 9568                (_ "(" ")" @end) @indent
 9569                (_ "{" "}" @end) @indent
 9570            "#,
 9571        )
 9572        .unwrap(),
 9573    );
 9574
 9575    let text = "fn a() {}";
 9576
 9577    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9578    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9579    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9580    editor
 9581        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9582        .await;
 9583
 9584    editor.update_in(cx, |editor, window, cx| {
 9585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9586            s.select_ranges([5..5, 8..8, 9..9])
 9587        });
 9588        editor.newline(&Newline, window, cx);
 9589        assert_eq!(
 9590            editor.text(cx),
 9591            indoc!(
 9592                "
 9593                fn a(
 9594
 9595                ) {
 9596
 9597                }
 9598                "
 9599            )
 9600        );
 9601        assert_eq!(
 9602            editor.selections.ranges(cx),
 9603            &[
 9604                Point::new(1, 0)..Point::new(1, 0),
 9605                Point::new(3, 0)..Point::new(3, 0),
 9606                Point::new(5, 0)..Point::new(5, 0)
 9607            ]
 9608        );
 9609    });
 9610}
 9611
 9612#[gpui::test]
 9613async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9614    init_test(cx, |settings| {
 9615        settings.defaults.auto_indent = Some(true);
 9616        settings.languages.0.insert(
 9617            "python".into(),
 9618            LanguageSettingsContent {
 9619                auto_indent: Some(false),
 9620                ..Default::default()
 9621            },
 9622        );
 9623    });
 9624
 9625    let mut cx = EditorTestContext::new(cx).await;
 9626
 9627    let injected_language = Arc::new(
 9628        Language::new(
 9629            LanguageConfig {
 9630                brackets: BracketPairConfig {
 9631                    pairs: vec![
 9632                        BracketPair {
 9633                            start: "{".to_string(),
 9634                            end: "}".to_string(),
 9635                            close: false,
 9636                            surround: false,
 9637                            newline: true,
 9638                        },
 9639                        BracketPair {
 9640                            start: "(".to_string(),
 9641                            end: ")".to_string(),
 9642                            close: true,
 9643                            surround: false,
 9644                            newline: true,
 9645                        },
 9646                    ],
 9647                    ..Default::default()
 9648                },
 9649                name: "python".into(),
 9650                ..Default::default()
 9651            },
 9652            Some(tree_sitter_python::LANGUAGE.into()),
 9653        )
 9654        .with_indents_query(
 9655            r#"
 9656                (_ "(" ")" @end) @indent
 9657                (_ "{" "}" @end) @indent
 9658            "#,
 9659        )
 9660        .unwrap(),
 9661    );
 9662
 9663    let language = Arc::new(
 9664        Language::new(
 9665            LanguageConfig {
 9666                brackets: BracketPairConfig {
 9667                    pairs: vec![
 9668                        BracketPair {
 9669                            start: "{".to_string(),
 9670                            end: "}".to_string(),
 9671                            close: false,
 9672                            surround: false,
 9673                            newline: true,
 9674                        },
 9675                        BracketPair {
 9676                            start: "(".to_string(),
 9677                            end: ")".to_string(),
 9678                            close: true,
 9679                            surround: false,
 9680                            newline: true,
 9681                        },
 9682                    ],
 9683                    ..Default::default()
 9684                },
 9685                name: LanguageName::new("rust"),
 9686                ..Default::default()
 9687            },
 9688            Some(tree_sitter_rust::LANGUAGE.into()),
 9689        )
 9690        .with_indents_query(
 9691            r#"
 9692                (_ "(" ")" @end) @indent
 9693                (_ "{" "}" @end) @indent
 9694            "#,
 9695        )
 9696        .unwrap()
 9697        .with_injection_query(
 9698            r#"
 9699            (macro_invocation
 9700                macro: (identifier) @_macro_name
 9701                (token_tree) @injection.content
 9702                (#set! injection.language "python"))
 9703           "#,
 9704        )
 9705        .unwrap(),
 9706    );
 9707
 9708    cx.language_registry().add(injected_language);
 9709    cx.language_registry().add(language.clone());
 9710
 9711    cx.update_buffer(|buffer, cx| {
 9712        buffer.set_language(Some(language), cx);
 9713    });
 9714
 9715    cx.set_state(r#"struct A {ˇ}"#);
 9716
 9717    cx.update_editor(|editor, window, cx| {
 9718        editor.newline(&Default::default(), window, cx);
 9719    });
 9720
 9721    cx.assert_editor_state(indoc!(
 9722        "struct A {
 9723            ˇ
 9724        }"
 9725    ));
 9726
 9727    cx.set_state(r#"select_biased!(ˇ)"#);
 9728
 9729    cx.update_editor(|editor, window, cx| {
 9730        editor.newline(&Default::default(), window, cx);
 9731        editor.handle_input("def ", window, cx);
 9732        editor.handle_input("(", window, cx);
 9733        editor.newline(&Default::default(), window, cx);
 9734        editor.handle_input("a", window, cx);
 9735    });
 9736
 9737    cx.assert_editor_state(indoc!(
 9738        "select_biased!(
 9739        def (
 9740 9741        )
 9742        )"
 9743    ));
 9744}
 9745
 9746#[gpui::test]
 9747async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9748    init_test(cx, |_| {});
 9749
 9750    {
 9751        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9752        cx.set_state(indoc! {"
 9753            impl A {
 9754
 9755                fn b() {}
 9756
 9757            «fn c() {
 9758
 9759            }ˇ»
 9760            }
 9761        "});
 9762
 9763        cx.update_editor(|editor, window, cx| {
 9764            editor.autoindent(&Default::default(), window, cx);
 9765        });
 9766
 9767        cx.assert_editor_state(indoc! {"
 9768            impl A {
 9769
 9770                fn b() {}
 9771
 9772                «fn c() {
 9773
 9774                }ˇ»
 9775            }
 9776        "});
 9777    }
 9778
 9779    {
 9780        let mut cx = EditorTestContext::new_multibuffer(
 9781            cx,
 9782            [indoc! { "
 9783                impl A {
 9784                «
 9785                // a
 9786                fn b(){}
 9787                »
 9788                «
 9789                    }
 9790                    fn c(){}
 9791                »
 9792            "}],
 9793        );
 9794
 9795        let buffer = cx.update_editor(|editor, _, cx| {
 9796            let buffer = editor.buffer().update(cx, |buffer, _| {
 9797                buffer.all_buffers().iter().next().unwrap().clone()
 9798            });
 9799            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9800            buffer
 9801        });
 9802
 9803        cx.run_until_parked();
 9804        cx.update_editor(|editor, window, cx| {
 9805            editor.select_all(&Default::default(), window, cx);
 9806            editor.autoindent(&Default::default(), window, cx)
 9807        });
 9808        cx.run_until_parked();
 9809
 9810        cx.update(|_, cx| {
 9811            assert_eq!(
 9812                buffer.read(cx).text(),
 9813                indoc! { "
 9814                    impl A {
 9815
 9816                        // a
 9817                        fn b(){}
 9818
 9819
 9820                    }
 9821                    fn c(){}
 9822
 9823                " }
 9824            )
 9825        });
 9826    }
 9827}
 9828
 9829#[gpui::test]
 9830async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9831    init_test(cx, |_| {});
 9832
 9833    let mut cx = EditorTestContext::new(cx).await;
 9834
 9835    let language = Arc::new(Language::new(
 9836        LanguageConfig {
 9837            brackets: BracketPairConfig {
 9838                pairs: vec![
 9839                    BracketPair {
 9840                        start: "{".to_string(),
 9841                        end: "}".to_string(),
 9842                        close: true,
 9843                        surround: true,
 9844                        newline: true,
 9845                    },
 9846                    BracketPair {
 9847                        start: "(".to_string(),
 9848                        end: ")".to_string(),
 9849                        close: true,
 9850                        surround: true,
 9851                        newline: true,
 9852                    },
 9853                    BracketPair {
 9854                        start: "/*".to_string(),
 9855                        end: " */".to_string(),
 9856                        close: true,
 9857                        surround: true,
 9858                        newline: true,
 9859                    },
 9860                    BracketPair {
 9861                        start: "[".to_string(),
 9862                        end: "]".to_string(),
 9863                        close: false,
 9864                        surround: false,
 9865                        newline: true,
 9866                    },
 9867                    BracketPair {
 9868                        start: "\"".to_string(),
 9869                        end: "\"".to_string(),
 9870                        close: true,
 9871                        surround: true,
 9872                        newline: false,
 9873                    },
 9874                    BracketPair {
 9875                        start: "<".to_string(),
 9876                        end: ">".to_string(),
 9877                        close: false,
 9878                        surround: true,
 9879                        newline: true,
 9880                    },
 9881                ],
 9882                ..Default::default()
 9883            },
 9884            autoclose_before: "})]".to_string(),
 9885            ..Default::default()
 9886        },
 9887        Some(tree_sitter_rust::LANGUAGE.into()),
 9888    ));
 9889
 9890    cx.language_registry().add(language.clone());
 9891    cx.update_buffer(|buffer, cx| {
 9892        buffer.set_language(Some(language), cx);
 9893    });
 9894
 9895    cx.set_state(
 9896        &r#"
 9897            🏀ˇ
 9898            εˇ
 9899            ❤️ˇ
 9900        "#
 9901        .unindent(),
 9902    );
 9903
 9904    // autoclose multiple nested brackets at multiple cursors
 9905    cx.update_editor(|editor, window, cx| {
 9906        editor.handle_input("{", window, cx);
 9907        editor.handle_input("{", window, cx);
 9908        editor.handle_input("{", window, cx);
 9909    });
 9910    cx.assert_editor_state(
 9911        &"
 9912            🏀{{{ˇ}}}
 9913            ε{{{ˇ}}}
 9914            ❤️{{{ˇ}}}
 9915        "
 9916        .unindent(),
 9917    );
 9918
 9919    // insert a different closing bracket
 9920    cx.update_editor(|editor, window, cx| {
 9921        editor.handle_input(")", window, cx);
 9922    });
 9923    cx.assert_editor_state(
 9924        &"
 9925            🏀{{{)ˇ}}}
 9926            ε{{{)ˇ}}}
 9927            ❤️{{{)ˇ}}}
 9928        "
 9929        .unindent(),
 9930    );
 9931
 9932    // skip over the auto-closed brackets when typing a closing bracket
 9933    cx.update_editor(|editor, window, cx| {
 9934        editor.move_right(&MoveRight, window, cx);
 9935        editor.handle_input("}", window, cx);
 9936        editor.handle_input("}", window, cx);
 9937        editor.handle_input("}", window, cx);
 9938    });
 9939    cx.assert_editor_state(
 9940        &"
 9941            🏀{{{)}}}}ˇ
 9942            ε{{{)}}}}ˇ
 9943            ❤️{{{)}}}}ˇ
 9944        "
 9945        .unindent(),
 9946    );
 9947
 9948    // autoclose multi-character pairs
 9949    cx.set_state(
 9950        &"
 9951            ˇ
 9952            ˇ
 9953        "
 9954        .unindent(),
 9955    );
 9956    cx.update_editor(|editor, window, cx| {
 9957        editor.handle_input("/", window, cx);
 9958        editor.handle_input("*", window, cx);
 9959    });
 9960    cx.assert_editor_state(
 9961        &"
 9962            /*ˇ */
 9963            /*ˇ */
 9964        "
 9965        .unindent(),
 9966    );
 9967
 9968    // one cursor autocloses a multi-character pair, one cursor
 9969    // does not autoclose.
 9970    cx.set_state(
 9971        &"
 9972 9973            ˇ
 9974        "
 9975        .unindent(),
 9976    );
 9977    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9978    cx.assert_editor_state(
 9979        &"
 9980            /*ˇ */
 9981 9982        "
 9983        .unindent(),
 9984    );
 9985
 9986    // Don't autoclose if the next character isn't whitespace and isn't
 9987    // listed in the language's "autoclose_before" section.
 9988    cx.set_state("ˇa b");
 9989    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9990    cx.assert_editor_state("{ˇa b");
 9991
 9992    // Don't autoclose if `close` is false for the bracket pair
 9993    cx.set_state("ˇ");
 9994    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9995    cx.assert_editor_state("");
 9996
 9997    // Surround with brackets if text is selected
 9998    cx.set_state("«aˇ» b");
 9999    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10000    cx.assert_editor_state("{«aˇ»} b");
10001
10002    // Autoclose when not immediately after a word character
10003    cx.set_state("a ˇ");
10004    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10005    cx.assert_editor_state("a \"ˇ\"");
10006
10007    // Autoclose pair where the start and end characters are the same
10008    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10009    cx.assert_editor_state("a \"\"ˇ");
10010
10011    // Don't autoclose when immediately after a word character
10012    cx.set_state("");
10013    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10014    cx.assert_editor_state("a\"ˇ");
10015
10016    // Do autoclose when after a non-word character
10017    cx.set_state("");
10018    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10019    cx.assert_editor_state("{\"ˇ\"");
10020
10021    // Non identical pairs autoclose regardless of preceding character
10022    cx.set_state("");
10023    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10024    cx.assert_editor_state("a{ˇ}");
10025
10026    // Don't autoclose pair if autoclose is disabled
10027    cx.set_state("ˇ");
10028    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10029    cx.assert_editor_state("");
10030
10031    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10032    cx.set_state("«aˇ» b");
10033    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10034    cx.assert_editor_state("<«aˇ»> b");
10035}
10036
10037#[gpui::test]
10038async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10039    init_test(cx, |settings| {
10040        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10041    });
10042
10043    let mut cx = EditorTestContext::new(cx).await;
10044
10045    let language = Arc::new(Language::new(
10046        LanguageConfig {
10047            brackets: BracketPairConfig {
10048                pairs: vec![
10049                    BracketPair {
10050                        start: "{".to_string(),
10051                        end: "}".to_string(),
10052                        close: true,
10053                        surround: true,
10054                        newline: true,
10055                    },
10056                    BracketPair {
10057                        start: "(".to_string(),
10058                        end: ")".to_string(),
10059                        close: true,
10060                        surround: true,
10061                        newline: true,
10062                    },
10063                    BracketPair {
10064                        start: "[".to_string(),
10065                        end: "]".to_string(),
10066                        close: false,
10067                        surround: false,
10068                        newline: true,
10069                    },
10070                ],
10071                ..Default::default()
10072            },
10073            autoclose_before: "})]".to_string(),
10074            ..Default::default()
10075        },
10076        Some(tree_sitter_rust::LANGUAGE.into()),
10077    ));
10078
10079    cx.language_registry().add(language.clone());
10080    cx.update_buffer(|buffer, cx| {
10081        buffer.set_language(Some(language), cx);
10082    });
10083
10084    cx.set_state(
10085        &"
10086            ˇ
10087            ˇ
10088            ˇ
10089        "
10090        .unindent(),
10091    );
10092
10093    // ensure only matching closing brackets are skipped over
10094    cx.update_editor(|editor, window, cx| {
10095        editor.handle_input("}", window, cx);
10096        editor.move_left(&MoveLeft, window, cx);
10097        editor.handle_input(")", window, cx);
10098        editor.move_left(&MoveLeft, window, cx);
10099    });
10100    cx.assert_editor_state(
10101        &"
10102            ˇ)}
10103            ˇ)}
10104            ˇ)}
10105        "
10106        .unindent(),
10107    );
10108
10109    // skip-over closing brackets at multiple cursors
10110    cx.update_editor(|editor, window, cx| {
10111        editor.handle_input(")", window, cx);
10112        editor.handle_input("}", window, cx);
10113    });
10114    cx.assert_editor_state(
10115        &"
10116            )}ˇ
10117            )}ˇ
10118            )}ˇ
10119        "
10120        .unindent(),
10121    );
10122
10123    // ignore non-close brackets
10124    cx.update_editor(|editor, window, cx| {
10125        editor.handle_input("]", window, cx);
10126        editor.move_left(&MoveLeft, window, cx);
10127        editor.handle_input("]", window, cx);
10128    });
10129    cx.assert_editor_state(
10130        &"
10131            )}]ˇ]
10132            )}]ˇ]
10133            )}]ˇ]
10134        "
10135        .unindent(),
10136    );
10137}
10138
10139#[gpui::test]
10140async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10141    init_test(cx, |_| {});
10142
10143    let mut cx = EditorTestContext::new(cx).await;
10144
10145    let html_language = Arc::new(
10146        Language::new(
10147            LanguageConfig {
10148                name: "HTML".into(),
10149                brackets: BracketPairConfig {
10150                    pairs: vec![
10151                        BracketPair {
10152                            start: "<".into(),
10153                            end: ">".into(),
10154                            close: true,
10155                            ..Default::default()
10156                        },
10157                        BracketPair {
10158                            start: "{".into(),
10159                            end: "}".into(),
10160                            close: true,
10161                            ..Default::default()
10162                        },
10163                        BracketPair {
10164                            start: "(".into(),
10165                            end: ")".into(),
10166                            close: true,
10167                            ..Default::default()
10168                        },
10169                    ],
10170                    ..Default::default()
10171                },
10172                autoclose_before: "})]>".into(),
10173                ..Default::default()
10174            },
10175            Some(tree_sitter_html::LANGUAGE.into()),
10176        )
10177        .with_injection_query(
10178            r#"
10179            (script_element
10180                (raw_text) @injection.content
10181                (#set! injection.language "javascript"))
10182            "#,
10183        )
10184        .unwrap(),
10185    );
10186
10187    let javascript_language = Arc::new(Language::new(
10188        LanguageConfig {
10189            name: "JavaScript".into(),
10190            brackets: BracketPairConfig {
10191                pairs: vec![
10192                    BracketPair {
10193                        start: "/*".into(),
10194                        end: " */".into(),
10195                        close: true,
10196                        ..Default::default()
10197                    },
10198                    BracketPair {
10199                        start: "{".into(),
10200                        end: "}".into(),
10201                        close: true,
10202                        ..Default::default()
10203                    },
10204                    BracketPair {
10205                        start: "(".into(),
10206                        end: ")".into(),
10207                        close: true,
10208                        ..Default::default()
10209                    },
10210                ],
10211                ..Default::default()
10212            },
10213            autoclose_before: "})]>".into(),
10214            ..Default::default()
10215        },
10216        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10217    ));
10218
10219    cx.language_registry().add(html_language.clone());
10220    cx.language_registry().add(javascript_language);
10221    cx.executor().run_until_parked();
10222
10223    cx.update_buffer(|buffer, cx| {
10224        buffer.set_language(Some(html_language), cx);
10225    });
10226
10227    cx.set_state(
10228        &r#"
10229            <body>ˇ
10230                <script>
10231                    var x = 1;ˇ
10232                </script>
10233            </body>ˇ
10234        "#
10235        .unindent(),
10236    );
10237
10238    // Precondition: different languages are active at different locations.
10239    cx.update_editor(|editor, window, cx| {
10240        let snapshot = editor.snapshot(window, cx);
10241        let cursors = editor.selections.ranges::<usize>(cx);
10242        let languages = cursors
10243            .iter()
10244            .map(|c| snapshot.language_at(c.start).unwrap().name())
10245            .collect::<Vec<_>>();
10246        assert_eq!(
10247            languages,
10248            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10249        );
10250    });
10251
10252    // Angle brackets autoclose in HTML, but not JavaScript.
10253    cx.update_editor(|editor, window, cx| {
10254        editor.handle_input("<", window, cx);
10255        editor.handle_input("a", window, cx);
10256    });
10257    cx.assert_editor_state(
10258        &r#"
10259            <body><aˇ>
10260                <script>
10261                    var x = 1;<aˇ
10262                </script>
10263            </body><aˇ>
10264        "#
10265        .unindent(),
10266    );
10267
10268    // Curly braces and parens autoclose in both HTML and JavaScript.
10269    cx.update_editor(|editor, window, cx| {
10270        editor.handle_input(" b=", window, cx);
10271        editor.handle_input("{", window, cx);
10272        editor.handle_input("c", window, cx);
10273        editor.handle_input("(", window, cx);
10274    });
10275    cx.assert_editor_state(
10276        &r#"
10277            <body><a b={c(ˇ)}>
10278                <script>
10279                    var x = 1;<a b={c(ˇ)}
10280                </script>
10281            </body><a b={c(ˇ)}>
10282        "#
10283        .unindent(),
10284    );
10285
10286    // Brackets that were already autoclosed are skipped.
10287    cx.update_editor(|editor, window, cx| {
10288        editor.handle_input(")", window, cx);
10289        editor.handle_input("d", window, cx);
10290        editor.handle_input("}", window, cx);
10291    });
10292    cx.assert_editor_state(
10293        &r#"
10294            <body><a b={c()d}ˇ>
10295                <script>
10296                    var x = 1;<a b={c()d}ˇ
10297                </script>
10298            </body><a b={c()d}ˇ>
10299        "#
10300        .unindent(),
10301    );
10302    cx.update_editor(|editor, window, cx| {
10303        editor.handle_input(">", window, cx);
10304    });
10305    cx.assert_editor_state(
10306        &r#"
10307            <body><a b={c()d}>ˇ
10308                <script>
10309                    var x = 1;<a b={c()d}>ˇ
10310                </script>
10311            </body><a b={c()d}>ˇ
10312        "#
10313        .unindent(),
10314    );
10315
10316    // Reset
10317    cx.set_state(
10318        &r#"
10319            <body>ˇ
10320                <script>
10321                    var x = 1;ˇ
10322                </script>
10323            </body>ˇ
10324        "#
10325        .unindent(),
10326    );
10327
10328    cx.update_editor(|editor, window, cx| {
10329        editor.handle_input("<", window, cx);
10330    });
10331    cx.assert_editor_state(
10332        &r#"
10333            <body><ˇ>
10334                <script>
10335                    var x = 1;<ˇ
10336                </script>
10337            </body><ˇ>
10338        "#
10339        .unindent(),
10340    );
10341
10342    // When backspacing, the closing angle brackets are removed.
10343    cx.update_editor(|editor, window, cx| {
10344        editor.backspace(&Backspace, window, cx);
10345    });
10346    cx.assert_editor_state(
10347        &r#"
10348            <body>ˇ
10349                <script>
10350                    var x = 1;ˇ
10351                </script>
10352            </body>ˇ
10353        "#
10354        .unindent(),
10355    );
10356
10357    // Block comments autoclose in JavaScript, but not HTML.
10358    cx.update_editor(|editor, window, cx| {
10359        editor.handle_input("/", window, cx);
10360        editor.handle_input("*", window, cx);
10361    });
10362    cx.assert_editor_state(
10363        &r#"
10364            <body>/*ˇ
10365                <script>
10366                    var x = 1;/*ˇ */
10367                </script>
10368            </body>/*ˇ
10369        "#
10370        .unindent(),
10371    );
10372}
10373
10374#[gpui::test]
10375async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10376    init_test(cx, |_| {});
10377
10378    let mut cx = EditorTestContext::new(cx).await;
10379
10380    let rust_language = Arc::new(
10381        Language::new(
10382            LanguageConfig {
10383                name: "Rust".into(),
10384                brackets: serde_json::from_value(json!([
10385                    { "start": "{", "end": "}", "close": true, "newline": true },
10386                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10387                ]))
10388                .unwrap(),
10389                autoclose_before: "})]>".into(),
10390                ..Default::default()
10391            },
10392            Some(tree_sitter_rust::LANGUAGE.into()),
10393        )
10394        .with_override_query("(string_literal) @string")
10395        .unwrap(),
10396    );
10397
10398    cx.language_registry().add(rust_language.clone());
10399    cx.update_buffer(|buffer, cx| {
10400        buffer.set_language(Some(rust_language), cx);
10401    });
10402
10403    cx.set_state(
10404        &r#"
10405            let x = ˇ
10406        "#
10407        .unindent(),
10408    );
10409
10410    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10411    cx.update_editor(|editor, window, cx| {
10412        editor.handle_input("\"", window, cx);
10413    });
10414    cx.assert_editor_state(
10415        &r#"
10416            let x = "ˇ"
10417        "#
10418        .unindent(),
10419    );
10420
10421    // Inserting another quotation mark. The cursor moves across the existing
10422    // automatically-inserted quotation mark.
10423    cx.update_editor(|editor, window, cx| {
10424        editor.handle_input("\"", window, cx);
10425    });
10426    cx.assert_editor_state(
10427        &r#"
10428            let x = ""ˇ
10429        "#
10430        .unindent(),
10431    );
10432
10433    // Reset
10434    cx.set_state(
10435        &r#"
10436            let x = ˇ
10437        "#
10438        .unindent(),
10439    );
10440
10441    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10442    cx.update_editor(|editor, window, cx| {
10443        editor.handle_input("\"", window, cx);
10444        editor.handle_input(" ", window, cx);
10445        editor.move_left(&Default::default(), window, cx);
10446        editor.handle_input("\\", window, cx);
10447        editor.handle_input("\"", window, cx);
10448    });
10449    cx.assert_editor_state(
10450        &r#"
10451            let x = "\"ˇ "
10452        "#
10453        .unindent(),
10454    );
10455
10456    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10457    // mark. Nothing is inserted.
10458    cx.update_editor(|editor, window, cx| {
10459        editor.move_right(&Default::default(), window, cx);
10460        editor.handle_input("\"", window, cx);
10461    });
10462    cx.assert_editor_state(
10463        &r#"
10464            let x = "\" "ˇ
10465        "#
10466        .unindent(),
10467    );
10468}
10469
10470#[gpui::test]
10471async fn test_surround_with_pair(cx: &mut TestAppContext) {
10472    init_test(cx, |_| {});
10473
10474    let language = Arc::new(Language::new(
10475        LanguageConfig {
10476            brackets: BracketPairConfig {
10477                pairs: vec![
10478                    BracketPair {
10479                        start: "{".to_string(),
10480                        end: "}".to_string(),
10481                        close: true,
10482                        surround: true,
10483                        newline: true,
10484                    },
10485                    BracketPair {
10486                        start: "/* ".to_string(),
10487                        end: "*/".to_string(),
10488                        close: true,
10489                        surround: true,
10490                        ..Default::default()
10491                    },
10492                ],
10493                ..Default::default()
10494            },
10495            ..Default::default()
10496        },
10497        Some(tree_sitter_rust::LANGUAGE.into()),
10498    ));
10499
10500    let text = r#"
10501        a
10502        b
10503        c
10504    "#
10505    .unindent();
10506
10507    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10508    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10509    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10510    editor
10511        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10512        .await;
10513
10514    editor.update_in(cx, |editor, window, cx| {
10515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10516            s.select_display_ranges([
10517                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10518                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10519                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10520            ])
10521        });
10522
10523        editor.handle_input("{", window, cx);
10524        editor.handle_input("{", window, cx);
10525        editor.handle_input("{", window, cx);
10526        assert_eq!(
10527            editor.text(cx),
10528            "
10529                {{{a}}}
10530                {{{b}}}
10531                {{{c}}}
10532            "
10533            .unindent()
10534        );
10535        assert_eq!(
10536            editor.selections.display_ranges(cx),
10537            [
10538                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10539                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10540                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10541            ]
10542        );
10543
10544        editor.undo(&Undo, window, cx);
10545        editor.undo(&Undo, window, cx);
10546        editor.undo(&Undo, window, cx);
10547        assert_eq!(
10548            editor.text(cx),
10549            "
10550                a
10551                b
10552                c
10553            "
10554            .unindent()
10555        );
10556        assert_eq!(
10557            editor.selections.display_ranges(cx),
10558            [
10559                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10560                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10561                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10562            ]
10563        );
10564
10565        // Ensure inserting the first character of a multi-byte bracket pair
10566        // doesn't surround the selections with the bracket.
10567        editor.handle_input("/", window, cx);
10568        assert_eq!(
10569            editor.text(cx),
10570            "
10571                /
10572                /
10573                /
10574            "
10575            .unindent()
10576        );
10577        assert_eq!(
10578            editor.selections.display_ranges(cx),
10579            [
10580                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10581                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10582                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10583            ]
10584        );
10585
10586        editor.undo(&Undo, window, cx);
10587        assert_eq!(
10588            editor.text(cx),
10589            "
10590                a
10591                b
10592                c
10593            "
10594            .unindent()
10595        );
10596        assert_eq!(
10597            editor.selections.display_ranges(cx),
10598            [
10599                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10600                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10601                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10602            ]
10603        );
10604
10605        // Ensure inserting the last character of a multi-byte bracket pair
10606        // doesn't surround the selections with the bracket.
10607        editor.handle_input("*", window, cx);
10608        assert_eq!(
10609            editor.text(cx),
10610            "
10611                *
10612                *
10613                *
10614            "
10615            .unindent()
10616        );
10617        assert_eq!(
10618            editor.selections.display_ranges(cx),
10619            [
10620                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10621                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10622                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10623            ]
10624        );
10625    });
10626}
10627
10628#[gpui::test]
10629async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10630    init_test(cx, |_| {});
10631
10632    let language = Arc::new(Language::new(
10633        LanguageConfig {
10634            brackets: BracketPairConfig {
10635                pairs: vec![BracketPair {
10636                    start: "{".to_string(),
10637                    end: "}".to_string(),
10638                    close: true,
10639                    surround: true,
10640                    newline: true,
10641                }],
10642                ..Default::default()
10643            },
10644            autoclose_before: "}".to_string(),
10645            ..Default::default()
10646        },
10647        Some(tree_sitter_rust::LANGUAGE.into()),
10648    ));
10649
10650    let text = r#"
10651        a
10652        b
10653        c
10654    "#
10655    .unindent();
10656
10657    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10658    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10659    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10660    editor
10661        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10662        .await;
10663
10664    editor.update_in(cx, |editor, window, cx| {
10665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10666            s.select_ranges([
10667                Point::new(0, 1)..Point::new(0, 1),
10668                Point::new(1, 1)..Point::new(1, 1),
10669                Point::new(2, 1)..Point::new(2, 1),
10670            ])
10671        });
10672
10673        editor.handle_input("{", window, cx);
10674        editor.handle_input("{", window, cx);
10675        editor.handle_input("_", window, cx);
10676        assert_eq!(
10677            editor.text(cx),
10678            "
10679                a{{_}}
10680                b{{_}}
10681                c{{_}}
10682            "
10683            .unindent()
10684        );
10685        assert_eq!(
10686            editor.selections.ranges::<Point>(cx),
10687            [
10688                Point::new(0, 4)..Point::new(0, 4),
10689                Point::new(1, 4)..Point::new(1, 4),
10690                Point::new(2, 4)..Point::new(2, 4)
10691            ]
10692        );
10693
10694        editor.backspace(&Default::default(), window, cx);
10695        editor.backspace(&Default::default(), window, cx);
10696        assert_eq!(
10697            editor.text(cx),
10698            "
10699                a{}
10700                b{}
10701                c{}
10702            "
10703            .unindent()
10704        );
10705        assert_eq!(
10706            editor.selections.ranges::<Point>(cx),
10707            [
10708                Point::new(0, 2)..Point::new(0, 2),
10709                Point::new(1, 2)..Point::new(1, 2),
10710                Point::new(2, 2)..Point::new(2, 2)
10711            ]
10712        );
10713
10714        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10715        assert_eq!(
10716            editor.text(cx),
10717            "
10718                a
10719                b
10720                c
10721            "
10722            .unindent()
10723        );
10724        assert_eq!(
10725            editor.selections.ranges::<Point>(cx),
10726            [
10727                Point::new(0, 1)..Point::new(0, 1),
10728                Point::new(1, 1)..Point::new(1, 1),
10729                Point::new(2, 1)..Point::new(2, 1)
10730            ]
10731        );
10732    });
10733}
10734
10735#[gpui::test]
10736async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10737    init_test(cx, |settings| {
10738        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10739    });
10740
10741    let mut cx = EditorTestContext::new(cx).await;
10742
10743    let language = Arc::new(Language::new(
10744        LanguageConfig {
10745            brackets: BracketPairConfig {
10746                pairs: vec![
10747                    BracketPair {
10748                        start: "{".to_string(),
10749                        end: "}".to_string(),
10750                        close: true,
10751                        surround: true,
10752                        newline: true,
10753                    },
10754                    BracketPair {
10755                        start: "(".to_string(),
10756                        end: ")".to_string(),
10757                        close: true,
10758                        surround: true,
10759                        newline: true,
10760                    },
10761                    BracketPair {
10762                        start: "[".to_string(),
10763                        end: "]".to_string(),
10764                        close: false,
10765                        surround: true,
10766                        newline: true,
10767                    },
10768                ],
10769                ..Default::default()
10770            },
10771            autoclose_before: "})]".to_string(),
10772            ..Default::default()
10773        },
10774        Some(tree_sitter_rust::LANGUAGE.into()),
10775    ));
10776
10777    cx.language_registry().add(language.clone());
10778    cx.update_buffer(|buffer, cx| {
10779        buffer.set_language(Some(language), cx);
10780    });
10781
10782    cx.set_state(
10783        &"
10784            {(ˇ)}
10785            [[ˇ]]
10786            {(ˇ)}
10787        "
10788        .unindent(),
10789    );
10790
10791    cx.update_editor(|editor, window, cx| {
10792        editor.backspace(&Default::default(), window, cx);
10793        editor.backspace(&Default::default(), window, cx);
10794    });
10795
10796    cx.assert_editor_state(
10797        &"
10798            ˇ
10799            ˇ]]
10800            ˇ
10801        "
10802        .unindent(),
10803    );
10804
10805    cx.update_editor(|editor, window, cx| {
10806        editor.handle_input("{", window, cx);
10807        editor.handle_input("{", window, cx);
10808        editor.move_right(&MoveRight, window, cx);
10809        editor.move_right(&MoveRight, window, cx);
10810        editor.move_left(&MoveLeft, window, cx);
10811        editor.move_left(&MoveLeft, window, cx);
10812        editor.backspace(&Default::default(), window, cx);
10813    });
10814
10815    cx.assert_editor_state(
10816        &"
10817            {ˇ}
10818            {ˇ}]]
10819            {ˇ}
10820        "
10821        .unindent(),
10822    );
10823
10824    cx.update_editor(|editor, window, cx| {
10825        editor.backspace(&Default::default(), window, cx);
10826    });
10827
10828    cx.assert_editor_state(
10829        &"
10830            ˇ
10831            ˇ]]
10832            ˇ
10833        "
10834        .unindent(),
10835    );
10836}
10837
10838#[gpui::test]
10839async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10840    init_test(cx, |_| {});
10841
10842    let language = Arc::new(Language::new(
10843        LanguageConfig::default(),
10844        Some(tree_sitter_rust::LANGUAGE.into()),
10845    ));
10846
10847    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10848    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10849    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10850    editor
10851        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10852        .await;
10853
10854    editor.update_in(cx, |editor, window, cx| {
10855        editor.set_auto_replace_emoji_shortcode(true);
10856
10857        editor.handle_input("Hello ", window, cx);
10858        editor.handle_input(":wave", window, cx);
10859        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10860
10861        editor.handle_input(":", window, cx);
10862        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10863
10864        editor.handle_input(" :smile", window, cx);
10865        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10866
10867        editor.handle_input(":", window, cx);
10868        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10869
10870        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10871        editor.handle_input(":wave", window, cx);
10872        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10873
10874        editor.handle_input(":", window, cx);
10875        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10876
10877        editor.handle_input(":1", window, cx);
10878        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10879
10880        editor.handle_input(":", window, cx);
10881        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10882
10883        // Ensure shortcode does not get replaced when it is part of a word
10884        editor.handle_input(" Test:wave", window, cx);
10885        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10886
10887        editor.handle_input(":", window, cx);
10888        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10889
10890        editor.set_auto_replace_emoji_shortcode(false);
10891
10892        // Ensure shortcode does not get replaced when auto replace is off
10893        editor.handle_input(" :wave", window, cx);
10894        assert_eq!(
10895            editor.text(cx),
10896            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10897        );
10898
10899        editor.handle_input(":", window, cx);
10900        assert_eq!(
10901            editor.text(cx),
10902            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10903        );
10904    });
10905}
10906
10907#[gpui::test]
10908async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10909    init_test(cx, |_| {});
10910
10911    let (text, insertion_ranges) = marked_text_ranges(
10912        indoc! {"
10913            ˇ
10914        "},
10915        false,
10916    );
10917
10918    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10919    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10920
10921    _ = editor.update_in(cx, |editor, window, cx| {
10922        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10923
10924        editor
10925            .insert_snippet(&insertion_ranges, snippet, window, cx)
10926            .unwrap();
10927
10928        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10929            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10930            assert_eq!(editor.text(cx), expected_text);
10931            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10932        }
10933
10934        assert(
10935            editor,
10936            cx,
10937            indoc! {"
10938            type «» =•
10939            "},
10940        );
10941
10942        assert!(editor.context_menu_visible(), "There should be a matches");
10943    });
10944}
10945
10946#[gpui::test]
10947async fn test_snippets(cx: &mut TestAppContext) {
10948    init_test(cx, |_| {});
10949
10950    let mut cx = EditorTestContext::new(cx).await;
10951
10952    cx.set_state(indoc! {"
10953        a.ˇ b
10954        a.ˇ b
10955        a.ˇ b
10956    "});
10957
10958    cx.update_editor(|editor, window, cx| {
10959        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10960        let insertion_ranges = editor
10961            .selections
10962            .all(cx)
10963            .iter()
10964            .map(|s| s.range())
10965            .collect::<Vec<_>>();
10966        editor
10967            .insert_snippet(&insertion_ranges, snippet, window, cx)
10968            .unwrap();
10969    });
10970
10971    cx.assert_editor_state(indoc! {"
10972        a.f(«oneˇ», two, «threeˇ») b
10973        a.f(«oneˇ», two, «threeˇ») b
10974        a.f(«oneˇ», two, «threeˇ») b
10975    "});
10976
10977    // Can't move earlier than the first tab stop
10978    cx.update_editor(|editor, window, cx| {
10979        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10980    });
10981    cx.assert_editor_state(indoc! {"
10982        a.f(«oneˇ», two, «threeˇ») b
10983        a.f(«oneˇ», two, «threeˇ») b
10984        a.f(«oneˇ», two, «threeˇ») b
10985    "});
10986
10987    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10988    cx.assert_editor_state(indoc! {"
10989        a.f(one, «twoˇ», three) b
10990        a.f(one, «twoˇ», three) b
10991        a.f(one, «twoˇ», three) b
10992    "});
10993
10994    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10995    cx.assert_editor_state(indoc! {"
10996        a.f(«oneˇ», two, «threeˇ») b
10997        a.f(«oneˇ», two, «threeˇ») b
10998        a.f(«oneˇ», two, «threeˇ») b
10999    "});
11000
11001    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11002    cx.assert_editor_state(indoc! {"
11003        a.f(one, «twoˇ», three) b
11004        a.f(one, «twoˇ», three) b
11005        a.f(one, «twoˇ», three) b
11006    "});
11007    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11008    cx.assert_editor_state(indoc! {"
11009        a.f(one, two, three)ˇ b
11010        a.f(one, two, three)ˇ b
11011        a.f(one, two, three)ˇ b
11012    "});
11013
11014    // As soon as the last tab stop is reached, snippet state is gone
11015    cx.update_editor(|editor, window, cx| {
11016        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11017    });
11018    cx.assert_editor_state(indoc! {"
11019        a.f(one, two, three)ˇ b
11020        a.f(one, two, three)ˇ b
11021        a.f(one, two, three)ˇ b
11022    "});
11023}
11024
11025#[gpui::test]
11026async fn test_snippet_indentation(cx: &mut TestAppContext) {
11027    init_test(cx, |_| {});
11028
11029    let mut cx = EditorTestContext::new(cx).await;
11030
11031    cx.update_editor(|editor, window, cx| {
11032        let snippet = Snippet::parse(indoc! {"
11033            /*
11034             * Multiline comment with leading indentation
11035             *
11036             * $1
11037             */
11038            $0"})
11039        .unwrap();
11040        let insertion_ranges = editor
11041            .selections
11042            .all(cx)
11043            .iter()
11044            .map(|s| s.range())
11045            .collect::<Vec<_>>();
11046        editor
11047            .insert_snippet(&insertion_ranges, snippet, window, cx)
11048            .unwrap();
11049    });
11050
11051    cx.assert_editor_state(indoc! {"
11052        /*
11053         * Multiline comment with leading indentation
11054         *
11055         * ˇ
11056         */
11057    "});
11058
11059    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11060    cx.assert_editor_state(indoc! {"
11061        /*
11062         * Multiline comment with leading indentation
11063         *
11064         *•
11065         */
11066        ˇ"});
11067}
11068
11069#[gpui::test]
11070async fn test_document_format_during_save(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    let fs = FakeFs::new(cx.executor());
11074    fs.insert_file(path!("/file.rs"), Default::default()).await;
11075
11076    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11077
11078    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11079    language_registry.add(rust_lang());
11080    let mut fake_servers = language_registry.register_fake_lsp(
11081        "Rust",
11082        FakeLspAdapter {
11083            capabilities: lsp::ServerCapabilities {
11084                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11085                ..Default::default()
11086            },
11087            ..Default::default()
11088        },
11089    );
11090
11091    let buffer = project
11092        .update(cx, |project, cx| {
11093            project.open_local_buffer(path!("/file.rs"), cx)
11094        })
11095        .await
11096        .unwrap();
11097
11098    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11099    let (editor, cx) = cx.add_window_view(|window, cx| {
11100        build_editor_with_project(project.clone(), buffer, window, cx)
11101    });
11102    editor.update_in(cx, |editor, window, cx| {
11103        editor.set_text("one\ntwo\nthree\n", window, cx)
11104    });
11105    assert!(cx.read(|cx| editor.is_dirty(cx)));
11106
11107    cx.executor().start_waiting();
11108    let fake_server = fake_servers.next().await.unwrap();
11109
11110    {
11111        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11112            move |params, _| async move {
11113                assert_eq!(
11114                    params.text_document.uri,
11115                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11116                );
11117                assert_eq!(params.options.tab_size, 4);
11118                Ok(Some(vec![lsp::TextEdit::new(
11119                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11120                    ", ".to_string(),
11121                )]))
11122            },
11123        );
11124        let save = editor
11125            .update_in(cx, |editor, window, cx| {
11126                editor.save(
11127                    SaveOptions {
11128                        format: true,
11129                        autosave: false,
11130                    },
11131                    project.clone(),
11132                    window,
11133                    cx,
11134                )
11135            })
11136            .unwrap();
11137        cx.executor().start_waiting();
11138        save.await;
11139
11140        assert_eq!(
11141            editor.update(cx, |editor, cx| editor.text(cx)),
11142            "one, two\nthree\n"
11143        );
11144        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11145    }
11146
11147    {
11148        editor.update_in(cx, |editor, window, cx| {
11149            editor.set_text("one\ntwo\nthree\n", window, cx)
11150        });
11151        assert!(cx.read(|cx| editor.is_dirty(cx)));
11152
11153        // Ensure we can still save even if formatting hangs.
11154        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11155            move |params, _| async move {
11156                assert_eq!(
11157                    params.text_document.uri,
11158                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11159                );
11160                futures::future::pending::<()>().await;
11161                unreachable!()
11162            },
11163        );
11164        let save = editor
11165            .update_in(cx, |editor, window, cx| {
11166                editor.save(
11167                    SaveOptions {
11168                        format: true,
11169                        autosave: false,
11170                    },
11171                    project.clone(),
11172                    window,
11173                    cx,
11174                )
11175            })
11176            .unwrap();
11177        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11178        cx.executor().start_waiting();
11179        save.await;
11180        assert_eq!(
11181            editor.update(cx, |editor, cx| editor.text(cx)),
11182            "one\ntwo\nthree\n"
11183        );
11184    }
11185
11186    // Set rust language override and assert overridden tabsize is sent to language server
11187    update_test_language_settings(cx, |settings| {
11188        settings.languages.0.insert(
11189            "Rust".into(),
11190            LanguageSettingsContent {
11191                tab_size: NonZeroU32::new(8),
11192                ..Default::default()
11193            },
11194        );
11195    });
11196
11197    {
11198        editor.update_in(cx, |editor, window, cx| {
11199            editor.set_text("somehting_new\n", window, cx)
11200        });
11201        assert!(cx.read(|cx| editor.is_dirty(cx)));
11202        let _formatting_request_signal = fake_server
11203            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11204                assert_eq!(
11205                    params.text_document.uri,
11206                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11207                );
11208                assert_eq!(params.options.tab_size, 8);
11209                Ok(Some(vec![]))
11210            });
11211        let save = editor
11212            .update_in(cx, |editor, window, cx| {
11213                editor.save(
11214                    SaveOptions {
11215                        format: true,
11216                        autosave: false,
11217                    },
11218                    project.clone(),
11219                    window,
11220                    cx,
11221                )
11222            })
11223            .unwrap();
11224        cx.executor().start_waiting();
11225        save.await;
11226    }
11227}
11228
11229#[gpui::test]
11230async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11231    init_test(cx, |settings| {
11232        settings.defaults.ensure_final_newline_on_save = Some(false);
11233    });
11234
11235    let fs = FakeFs::new(cx.executor());
11236    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11237
11238    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11239
11240    let buffer = project
11241        .update(cx, |project, cx| {
11242            project.open_local_buffer(path!("/file.txt"), cx)
11243        })
11244        .await
11245        .unwrap();
11246
11247    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11248    let (editor, cx) = cx.add_window_view(|window, cx| {
11249        build_editor_with_project(project.clone(), buffer, window, cx)
11250    });
11251    editor.update_in(cx, |editor, window, cx| {
11252        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11253            s.select_ranges([0..0])
11254        });
11255    });
11256    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11257
11258    editor.update_in(cx, |editor, window, cx| {
11259        editor.handle_input("\n", window, cx)
11260    });
11261    cx.run_until_parked();
11262    save(&editor, &project, cx).await;
11263    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11264
11265    editor.update_in(cx, |editor, window, cx| {
11266        editor.undo(&Default::default(), window, cx);
11267    });
11268    save(&editor, &project, cx).await;
11269    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11270
11271    editor.update_in(cx, |editor, window, cx| {
11272        editor.redo(&Default::default(), window, cx);
11273    });
11274    cx.run_until_parked();
11275    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11276
11277    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11278        let save = editor
11279            .update_in(cx, |editor, window, cx| {
11280                editor.save(
11281                    SaveOptions {
11282                        format: true,
11283                        autosave: false,
11284                    },
11285                    project.clone(),
11286                    window,
11287                    cx,
11288                )
11289            })
11290            .unwrap();
11291        cx.executor().start_waiting();
11292        save.await;
11293        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11294    }
11295}
11296
11297#[gpui::test]
11298async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11299    init_test(cx, |_| {});
11300
11301    let cols = 4;
11302    let rows = 10;
11303    let sample_text_1 = sample_text(rows, cols, 'a');
11304    assert_eq!(
11305        sample_text_1,
11306        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11307    );
11308    let sample_text_2 = sample_text(rows, cols, 'l');
11309    assert_eq!(
11310        sample_text_2,
11311        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11312    );
11313    let sample_text_3 = sample_text(rows, cols, 'v');
11314    assert_eq!(
11315        sample_text_3,
11316        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11317    );
11318
11319    let fs = FakeFs::new(cx.executor());
11320    fs.insert_tree(
11321        path!("/a"),
11322        json!({
11323            "main.rs": sample_text_1,
11324            "other.rs": sample_text_2,
11325            "lib.rs": sample_text_3,
11326        }),
11327    )
11328    .await;
11329
11330    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11331    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11332    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11333
11334    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11335    language_registry.add(rust_lang());
11336    let mut fake_servers = language_registry.register_fake_lsp(
11337        "Rust",
11338        FakeLspAdapter {
11339            capabilities: lsp::ServerCapabilities {
11340                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11341                ..Default::default()
11342            },
11343            ..Default::default()
11344        },
11345    );
11346
11347    let worktree = project.update(cx, |project, cx| {
11348        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11349        assert_eq!(worktrees.len(), 1);
11350        worktrees.pop().unwrap()
11351    });
11352    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11353
11354    let buffer_1 = project
11355        .update(cx, |project, cx| {
11356            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11357        })
11358        .await
11359        .unwrap();
11360    let buffer_2 = project
11361        .update(cx, |project, cx| {
11362            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11363        })
11364        .await
11365        .unwrap();
11366    let buffer_3 = project
11367        .update(cx, |project, cx| {
11368            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11369        })
11370        .await
11371        .unwrap();
11372
11373    let multi_buffer = cx.new(|cx| {
11374        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11375        multi_buffer.push_excerpts(
11376            buffer_1.clone(),
11377            [
11378                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11379                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11380                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11381            ],
11382            cx,
11383        );
11384        multi_buffer.push_excerpts(
11385            buffer_2.clone(),
11386            [
11387                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11388                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11389                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11390            ],
11391            cx,
11392        );
11393        multi_buffer.push_excerpts(
11394            buffer_3.clone(),
11395            [
11396                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11397                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11398                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11399            ],
11400            cx,
11401        );
11402        multi_buffer
11403    });
11404    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11405        Editor::new(
11406            EditorMode::full(),
11407            multi_buffer,
11408            Some(project.clone()),
11409            window,
11410            cx,
11411        )
11412    });
11413
11414    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11415        editor.change_selections(
11416            SelectionEffects::scroll(Autoscroll::Next),
11417            window,
11418            cx,
11419            |s| s.select_ranges(Some(1..2)),
11420        );
11421        editor.insert("|one|two|three|", window, cx);
11422    });
11423    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11424    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11425        editor.change_selections(
11426            SelectionEffects::scroll(Autoscroll::Next),
11427            window,
11428            cx,
11429            |s| s.select_ranges(Some(60..70)),
11430        );
11431        editor.insert("|four|five|six|", window, cx);
11432    });
11433    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11434
11435    // First two buffers should be edited, but not the third one.
11436    assert_eq!(
11437        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11438        "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}",
11439    );
11440    buffer_1.update(cx, |buffer, _| {
11441        assert!(buffer.is_dirty());
11442        assert_eq!(
11443            buffer.text(),
11444            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11445        )
11446    });
11447    buffer_2.update(cx, |buffer, _| {
11448        assert!(buffer.is_dirty());
11449        assert_eq!(
11450            buffer.text(),
11451            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11452        )
11453    });
11454    buffer_3.update(cx, |buffer, _| {
11455        assert!(!buffer.is_dirty());
11456        assert_eq!(buffer.text(), sample_text_3,)
11457    });
11458    cx.executor().run_until_parked();
11459
11460    cx.executor().start_waiting();
11461    let save = multi_buffer_editor
11462        .update_in(cx, |editor, window, cx| {
11463            editor.save(
11464                SaveOptions {
11465                    format: true,
11466                    autosave: false,
11467                },
11468                project.clone(),
11469                window,
11470                cx,
11471            )
11472        })
11473        .unwrap();
11474
11475    let fake_server = fake_servers.next().await.unwrap();
11476    fake_server
11477        .server
11478        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11479            Ok(Some(vec![lsp::TextEdit::new(
11480                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11481                format!("[{} formatted]", params.text_document.uri),
11482            )]))
11483        })
11484        .detach();
11485    save.await;
11486
11487    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11488    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11489    assert_eq!(
11490        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11491        uri!(
11492            "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}"
11493        ),
11494    );
11495    buffer_1.update(cx, |buffer, _| {
11496        assert!(!buffer.is_dirty());
11497        assert_eq!(
11498            buffer.text(),
11499            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11500        )
11501    });
11502    buffer_2.update(cx, |buffer, _| {
11503        assert!(!buffer.is_dirty());
11504        assert_eq!(
11505            buffer.text(),
11506            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11507        )
11508    });
11509    buffer_3.update(cx, |buffer, _| {
11510        assert!(!buffer.is_dirty());
11511        assert_eq!(buffer.text(), sample_text_3,)
11512    });
11513}
11514
11515#[gpui::test]
11516async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11517    init_test(cx, |_| {});
11518
11519    let fs = FakeFs::new(cx.executor());
11520    fs.insert_tree(
11521        path!("/dir"),
11522        json!({
11523            "file1.rs": "fn main() { println!(\"hello\"); }",
11524            "file2.rs": "fn test() { println!(\"test\"); }",
11525            "file3.rs": "fn other() { println!(\"other\"); }\n",
11526        }),
11527    )
11528    .await;
11529
11530    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11531    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11532    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11533
11534    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11535    language_registry.add(rust_lang());
11536
11537    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11538    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11539
11540    // Open three buffers
11541    let buffer_1 = project
11542        .update(cx, |project, cx| {
11543            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11544        })
11545        .await
11546        .unwrap();
11547    let buffer_2 = project
11548        .update(cx, |project, cx| {
11549            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11550        })
11551        .await
11552        .unwrap();
11553    let buffer_3 = project
11554        .update(cx, |project, cx| {
11555            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11556        })
11557        .await
11558        .unwrap();
11559
11560    // Create a multi-buffer with all three buffers
11561    let multi_buffer = cx.new(|cx| {
11562        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11563        multi_buffer.push_excerpts(
11564            buffer_1.clone(),
11565            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11566            cx,
11567        );
11568        multi_buffer.push_excerpts(
11569            buffer_2.clone(),
11570            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11571            cx,
11572        );
11573        multi_buffer.push_excerpts(
11574            buffer_3.clone(),
11575            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11576            cx,
11577        );
11578        multi_buffer
11579    });
11580
11581    let editor = cx.new_window_entity(|window, cx| {
11582        Editor::new(
11583            EditorMode::full(),
11584            multi_buffer,
11585            Some(project.clone()),
11586            window,
11587            cx,
11588        )
11589    });
11590
11591    // Edit only the first buffer
11592    editor.update_in(cx, |editor, window, cx| {
11593        editor.change_selections(
11594            SelectionEffects::scroll(Autoscroll::Next),
11595            window,
11596            cx,
11597            |s| s.select_ranges(Some(10..10)),
11598        );
11599        editor.insert("// edited", window, cx);
11600    });
11601
11602    // Verify that only buffer 1 is dirty
11603    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11604    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11605    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11606
11607    // Get write counts after file creation (files were created with initial content)
11608    // We expect each file to have been written once during creation
11609    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11610    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11611    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11612
11613    // Perform autosave
11614    let save_task = editor.update_in(cx, |editor, window, cx| {
11615        editor.save(
11616            SaveOptions {
11617                format: true,
11618                autosave: true,
11619            },
11620            project.clone(),
11621            window,
11622            cx,
11623        )
11624    });
11625    save_task.await.unwrap();
11626
11627    // Only the dirty buffer should have been saved
11628    assert_eq!(
11629        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11630        1,
11631        "Buffer 1 was dirty, so it should have been written once during autosave"
11632    );
11633    assert_eq!(
11634        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11635        0,
11636        "Buffer 2 was clean, so it should not have been written during autosave"
11637    );
11638    assert_eq!(
11639        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11640        0,
11641        "Buffer 3 was clean, so it should not have been written during autosave"
11642    );
11643
11644    // Verify buffer states after autosave
11645    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11646    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11647    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11648
11649    // Now perform a manual save (format = true)
11650    let save_task = editor.update_in(cx, |editor, window, cx| {
11651        editor.save(
11652            SaveOptions {
11653                format: true,
11654                autosave: false,
11655            },
11656            project.clone(),
11657            window,
11658            cx,
11659        )
11660    });
11661    save_task.await.unwrap();
11662
11663    // During manual save, clean buffers don't get written to disk
11664    // They just get did_save called for language server notifications
11665    assert_eq!(
11666        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11667        1,
11668        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11669    );
11670    assert_eq!(
11671        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11672        0,
11673        "Buffer 2 should not have been written at all"
11674    );
11675    assert_eq!(
11676        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11677        0,
11678        "Buffer 3 should not have been written at all"
11679    );
11680}
11681
11682async fn setup_range_format_test(
11683    cx: &mut TestAppContext,
11684) -> (
11685    Entity<Project>,
11686    Entity<Editor>,
11687    &mut gpui::VisualTestContext,
11688    lsp::FakeLanguageServer,
11689) {
11690    init_test(cx, |_| {});
11691
11692    let fs = FakeFs::new(cx.executor());
11693    fs.insert_file(path!("/file.rs"), Default::default()).await;
11694
11695    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11696
11697    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11698    language_registry.add(rust_lang());
11699    let mut fake_servers = language_registry.register_fake_lsp(
11700        "Rust",
11701        FakeLspAdapter {
11702            capabilities: lsp::ServerCapabilities {
11703                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11704                ..lsp::ServerCapabilities::default()
11705            },
11706            ..FakeLspAdapter::default()
11707        },
11708    );
11709
11710    let buffer = project
11711        .update(cx, |project, cx| {
11712            project.open_local_buffer(path!("/file.rs"), cx)
11713        })
11714        .await
11715        .unwrap();
11716
11717    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11718    let (editor, cx) = cx.add_window_view(|window, cx| {
11719        build_editor_with_project(project.clone(), buffer, window, cx)
11720    });
11721
11722    cx.executor().start_waiting();
11723    let fake_server = fake_servers.next().await.unwrap();
11724
11725    (project, editor, cx, fake_server)
11726}
11727
11728#[gpui::test]
11729async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11730    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11731
11732    editor.update_in(cx, |editor, window, cx| {
11733        editor.set_text("one\ntwo\nthree\n", window, cx)
11734    });
11735    assert!(cx.read(|cx| editor.is_dirty(cx)));
11736
11737    let save = editor
11738        .update_in(cx, |editor, window, cx| {
11739            editor.save(
11740                SaveOptions {
11741                    format: true,
11742                    autosave: false,
11743                },
11744                project.clone(),
11745                window,
11746                cx,
11747            )
11748        })
11749        .unwrap();
11750    fake_server
11751        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11752            assert_eq!(
11753                params.text_document.uri,
11754                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11755            );
11756            assert_eq!(params.options.tab_size, 4);
11757            Ok(Some(vec![lsp::TextEdit::new(
11758                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11759                ", ".to_string(),
11760            )]))
11761        })
11762        .next()
11763        .await;
11764    cx.executor().start_waiting();
11765    save.await;
11766    assert_eq!(
11767        editor.update(cx, |editor, cx| editor.text(cx)),
11768        "one, two\nthree\n"
11769    );
11770    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11771}
11772
11773#[gpui::test]
11774async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11775    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11776
11777    editor.update_in(cx, |editor, window, cx| {
11778        editor.set_text("one\ntwo\nthree\n", window, cx)
11779    });
11780    assert!(cx.read(|cx| editor.is_dirty(cx)));
11781
11782    // Test that save still works when formatting hangs
11783    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
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 save = editor
11794        .update_in(cx, |editor, window, cx| {
11795            editor.save(
11796                SaveOptions {
11797                    format: true,
11798                    autosave: false,
11799                },
11800                project.clone(),
11801                window,
11802                cx,
11803            )
11804        })
11805        .unwrap();
11806    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11807    cx.executor().start_waiting();
11808    save.await;
11809    assert_eq!(
11810        editor.update(cx, |editor, cx| editor.text(cx)),
11811        "one\ntwo\nthree\n"
11812    );
11813    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11814}
11815
11816#[gpui::test]
11817async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11818    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11819
11820    // Buffer starts clean, no formatting should be requested
11821    let save = editor
11822        .update_in(cx, |editor, window, cx| {
11823            editor.save(
11824                SaveOptions {
11825                    format: false,
11826                    autosave: false,
11827                },
11828                project.clone(),
11829                window,
11830                cx,
11831            )
11832        })
11833        .unwrap();
11834    let _pending_format_request = fake_server
11835        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11836            panic!("Should not be invoked");
11837        })
11838        .next();
11839    cx.executor().start_waiting();
11840    save.await;
11841    cx.run_until_parked();
11842}
11843
11844#[gpui::test]
11845async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11846    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11847
11848    // Set Rust language override and assert overridden tabsize is sent to language server
11849    update_test_language_settings(cx, |settings| {
11850        settings.languages.0.insert(
11851            "Rust".into(),
11852            LanguageSettingsContent {
11853                tab_size: NonZeroU32::new(8),
11854                ..Default::default()
11855            },
11856        );
11857    });
11858
11859    editor.update_in(cx, |editor, window, cx| {
11860        editor.set_text("something_new\n", window, cx)
11861    });
11862    assert!(cx.read(|cx| editor.is_dirty(cx)));
11863    let save = editor
11864        .update_in(cx, |editor, window, cx| {
11865            editor.save(
11866                SaveOptions {
11867                    format: true,
11868                    autosave: false,
11869                },
11870                project.clone(),
11871                window,
11872                cx,
11873            )
11874        })
11875        .unwrap();
11876    fake_server
11877        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11878            assert_eq!(
11879                params.text_document.uri,
11880                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11881            );
11882            assert_eq!(params.options.tab_size, 8);
11883            Ok(Some(Vec::new()))
11884        })
11885        .next()
11886        .await;
11887    save.await;
11888}
11889
11890#[gpui::test]
11891async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11892    init_test(cx, |settings| {
11893        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11894            Formatter::LanguageServer { name: None },
11895        )))
11896    });
11897
11898    let fs = FakeFs::new(cx.executor());
11899    fs.insert_file(path!("/file.rs"), Default::default()).await;
11900
11901    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11902
11903    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11904    language_registry.add(Arc::new(Language::new(
11905        LanguageConfig {
11906            name: "Rust".into(),
11907            matcher: LanguageMatcher {
11908                path_suffixes: vec!["rs".to_string()],
11909                ..Default::default()
11910            },
11911            ..LanguageConfig::default()
11912        },
11913        Some(tree_sitter_rust::LANGUAGE.into()),
11914    )));
11915    update_test_language_settings(cx, |settings| {
11916        // Enable Prettier formatting for the same buffer, and ensure
11917        // LSP is called instead of Prettier.
11918        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11919    });
11920    let mut fake_servers = language_registry.register_fake_lsp(
11921        "Rust",
11922        FakeLspAdapter {
11923            capabilities: lsp::ServerCapabilities {
11924                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11925                ..Default::default()
11926            },
11927            ..Default::default()
11928        },
11929    );
11930
11931    let buffer = project
11932        .update(cx, |project, cx| {
11933            project.open_local_buffer(path!("/file.rs"), cx)
11934        })
11935        .await
11936        .unwrap();
11937
11938    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11939    let (editor, cx) = cx.add_window_view(|window, cx| {
11940        build_editor_with_project(project.clone(), buffer, window, cx)
11941    });
11942    editor.update_in(cx, |editor, window, cx| {
11943        editor.set_text("one\ntwo\nthree\n", window, cx)
11944    });
11945
11946    cx.executor().start_waiting();
11947    let fake_server = fake_servers.next().await.unwrap();
11948
11949    let format = editor
11950        .update_in(cx, |editor, window, cx| {
11951            editor.perform_format(
11952                project.clone(),
11953                FormatTrigger::Manual,
11954                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11955                window,
11956                cx,
11957            )
11958        })
11959        .unwrap();
11960    fake_server
11961        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11962            assert_eq!(
11963                params.text_document.uri,
11964                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11965            );
11966            assert_eq!(params.options.tab_size, 4);
11967            Ok(Some(vec![lsp::TextEdit::new(
11968                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11969                ", ".to_string(),
11970            )]))
11971        })
11972        .next()
11973        .await;
11974    cx.executor().start_waiting();
11975    format.await;
11976    assert_eq!(
11977        editor.update(cx, |editor, cx| editor.text(cx)),
11978        "one, two\nthree\n"
11979    );
11980
11981    editor.update_in(cx, |editor, window, cx| {
11982        editor.set_text("one\ntwo\nthree\n", window, cx)
11983    });
11984    // Ensure we don't lock if formatting hangs.
11985    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11986        move |params, _| async move {
11987            assert_eq!(
11988                params.text_document.uri,
11989                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11990            );
11991            futures::future::pending::<()>().await;
11992            unreachable!()
11993        },
11994    );
11995    let format = editor
11996        .update_in(cx, |editor, window, cx| {
11997            editor.perform_format(
11998                project,
11999                FormatTrigger::Manual,
12000                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12001                window,
12002                cx,
12003            )
12004        })
12005        .unwrap();
12006    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12007    cx.executor().start_waiting();
12008    format.await;
12009    assert_eq!(
12010        editor.update(cx, |editor, cx| editor.text(cx)),
12011        "one\ntwo\nthree\n"
12012    );
12013}
12014
12015#[gpui::test]
12016async fn test_multiple_formatters(cx: &mut TestAppContext) {
12017    init_test(cx, |settings| {
12018        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12019        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12020            Formatter::LanguageServer { name: None },
12021            Formatter::CodeAction("code-action-1".into()),
12022            Formatter::CodeAction("code-action-2".into()),
12023        ])))
12024    });
12025
12026    let fs = FakeFs::new(cx.executor());
12027    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12028        .await;
12029
12030    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12031    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12032    language_registry.add(rust_lang());
12033
12034    let mut fake_servers = language_registry.register_fake_lsp(
12035        "Rust",
12036        FakeLspAdapter {
12037            capabilities: lsp::ServerCapabilities {
12038                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12039                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12040                    commands: vec!["the-command-for-code-action-1".into()],
12041                    ..Default::default()
12042                }),
12043                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12044                ..Default::default()
12045            },
12046            ..Default::default()
12047        },
12048    );
12049
12050    let buffer = project
12051        .update(cx, |project, cx| {
12052            project.open_local_buffer(path!("/file.rs"), cx)
12053        })
12054        .await
12055        .unwrap();
12056
12057    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12058    let (editor, cx) = cx.add_window_view(|window, cx| {
12059        build_editor_with_project(project.clone(), buffer, window, cx)
12060    });
12061
12062    cx.executor().start_waiting();
12063
12064    let fake_server = fake_servers.next().await.unwrap();
12065    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12066        move |_params, _| async move {
12067            Ok(Some(vec![lsp::TextEdit::new(
12068                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12069                "applied-formatting\n".to_string(),
12070            )]))
12071        },
12072    );
12073    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12074        move |params, _| async move {
12075            let requested_code_actions = params.context.only.expect("Expected code action request");
12076            assert_eq!(requested_code_actions.len(), 1);
12077
12078            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12079            let code_action = match requested_code_actions[0].as_str() {
12080                "code-action-1" => lsp::CodeAction {
12081                    kind: Some("code-action-1".into()),
12082                    edit: Some(lsp::WorkspaceEdit::new(
12083                        [(
12084                            uri,
12085                            vec![lsp::TextEdit::new(
12086                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12087                                "applied-code-action-1-edit\n".to_string(),
12088                            )],
12089                        )]
12090                        .into_iter()
12091                        .collect(),
12092                    )),
12093                    command: Some(lsp::Command {
12094                        command: "the-command-for-code-action-1".into(),
12095                        ..Default::default()
12096                    }),
12097                    ..Default::default()
12098                },
12099                "code-action-2" => lsp::CodeAction {
12100                    kind: Some("code-action-2".into()),
12101                    edit: Some(lsp::WorkspaceEdit::new(
12102                        [(
12103                            uri,
12104                            vec![lsp::TextEdit::new(
12105                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12106                                "applied-code-action-2-edit\n".to_string(),
12107                            )],
12108                        )]
12109                        .into_iter()
12110                        .collect(),
12111                    )),
12112                    ..Default::default()
12113                },
12114                req => panic!("Unexpected code action request: {:?}", req),
12115            };
12116            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12117                code_action,
12118            )]))
12119        },
12120    );
12121
12122    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12123        move |params, _| async move { Ok(params) }
12124    });
12125
12126    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12127    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12128        let fake = fake_server.clone();
12129        let lock = command_lock.clone();
12130        move |params, _| {
12131            assert_eq!(params.command, "the-command-for-code-action-1");
12132            let fake = fake.clone();
12133            let lock = lock.clone();
12134            async move {
12135                lock.lock().await;
12136                fake.server
12137                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12138                        label: None,
12139                        edit: lsp::WorkspaceEdit {
12140                            changes: Some(
12141                                [(
12142                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12143                                    vec![lsp::TextEdit {
12144                                        range: lsp::Range::new(
12145                                            lsp::Position::new(0, 0),
12146                                            lsp::Position::new(0, 0),
12147                                        ),
12148                                        new_text: "applied-code-action-1-command\n".into(),
12149                                    }],
12150                                )]
12151                                .into_iter()
12152                                .collect(),
12153                            ),
12154                            ..Default::default()
12155                        },
12156                    })
12157                    .await
12158                    .into_response()
12159                    .unwrap();
12160                Ok(Some(json!(null)))
12161            }
12162        }
12163    });
12164
12165    cx.executor().start_waiting();
12166    editor
12167        .update_in(cx, |editor, window, cx| {
12168            editor.perform_format(
12169                project.clone(),
12170                FormatTrigger::Manual,
12171                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12172                window,
12173                cx,
12174            )
12175        })
12176        .unwrap()
12177        .await;
12178    editor.update(cx, |editor, cx| {
12179        assert_eq!(
12180            editor.text(cx),
12181            r#"
12182                applied-code-action-2-edit
12183                applied-code-action-1-command
12184                applied-code-action-1-edit
12185                applied-formatting
12186                one
12187                two
12188                three
12189            "#
12190            .unindent()
12191        );
12192    });
12193
12194    editor.update_in(cx, |editor, window, cx| {
12195        editor.undo(&Default::default(), window, cx);
12196        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12197    });
12198
12199    // Perform a manual edit while waiting for an LSP command
12200    // that's being run as part of a formatting code action.
12201    let lock_guard = command_lock.lock().await;
12202    let format = editor
12203        .update_in(cx, |editor, window, cx| {
12204            editor.perform_format(
12205                project.clone(),
12206                FormatTrigger::Manual,
12207                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12208                window,
12209                cx,
12210            )
12211        })
12212        .unwrap();
12213    cx.run_until_parked();
12214    editor.update(cx, |editor, cx| {
12215        assert_eq!(
12216            editor.text(cx),
12217            r#"
12218                applied-code-action-1-edit
12219                applied-formatting
12220                one
12221                two
12222                three
12223            "#
12224            .unindent()
12225        );
12226
12227        editor.buffer.update(cx, |buffer, cx| {
12228            let ix = buffer.len(cx);
12229            buffer.edit([(ix..ix, "edited\n")], None, cx);
12230        });
12231    });
12232
12233    // Allow the LSP command to proceed. Because the buffer was edited,
12234    // the second code action will not be run.
12235    drop(lock_guard);
12236    format.await;
12237    editor.update_in(cx, |editor, window, cx| {
12238        assert_eq!(
12239            editor.text(cx),
12240            r#"
12241                applied-code-action-1-command
12242                applied-code-action-1-edit
12243                applied-formatting
12244                one
12245                two
12246                three
12247                edited
12248            "#
12249            .unindent()
12250        );
12251
12252        // The manual edit is undone first, because it is the last thing the user did
12253        // (even though the command completed afterwards).
12254        editor.undo(&Default::default(), window, cx);
12255        assert_eq!(
12256            editor.text(cx),
12257            r#"
12258                applied-code-action-1-command
12259                applied-code-action-1-edit
12260                applied-formatting
12261                one
12262                two
12263                three
12264            "#
12265            .unindent()
12266        );
12267
12268        // All the formatting (including the command, which completed after the manual edit)
12269        // is undone together.
12270        editor.undo(&Default::default(), window, cx);
12271        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12272    });
12273}
12274
12275#[gpui::test]
12276async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12277    init_test(cx, |settings| {
12278        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12279            Formatter::LanguageServer { name: None },
12280        ])))
12281    });
12282
12283    let fs = FakeFs::new(cx.executor());
12284    fs.insert_file(path!("/file.ts"), Default::default()).await;
12285
12286    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12287
12288    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12289    language_registry.add(Arc::new(Language::new(
12290        LanguageConfig {
12291            name: "TypeScript".into(),
12292            matcher: LanguageMatcher {
12293                path_suffixes: vec!["ts".to_string()],
12294                ..Default::default()
12295            },
12296            ..LanguageConfig::default()
12297        },
12298        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12299    )));
12300    update_test_language_settings(cx, |settings| {
12301        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12302    });
12303    let mut fake_servers = language_registry.register_fake_lsp(
12304        "TypeScript",
12305        FakeLspAdapter {
12306            capabilities: lsp::ServerCapabilities {
12307                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12308                ..Default::default()
12309            },
12310            ..Default::default()
12311        },
12312    );
12313
12314    let buffer = project
12315        .update(cx, |project, cx| {
12316            project.open_local_buffer(path!("/file.ts"), cx)
12317        })
12318        .await
12319        .unwrap();
12320
12321    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12322    let (editor, cx) = cx.add_window_view(|window, cx| {
12323        build_editor_with_project(project.clone(), buffer, window, cx)
12324    });
12325    editor.update_in(cx, |editor, window, cx| {
12326        editor.set_text(
12327            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12328            window,
12329            cx,
12330        )
12331    });
12332
12333    cx.executor().start_waiting();
12334    let fake_server = fake_servers.next().await.unwrap();
12335
12336    let format = editor
12337        .update_in(cx, |editor, window, cx| {
12338            editor.perform_code_action_kind(
12339                project.clone(),
12340                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12341                window,
12342                cx,
12343            )
12344        })
12345        .unwrap();
12346    fake_server
12347        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12348            assert_eq!(
12349                params.text_document.uri,
12350                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12351            );
12352            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12353                lsp::CodeAction {
12354                    title: "Organize Imports".to_string(),
12355                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12356                    edit: Some(lsp::WorkspaceEdit {
12357                        changes: Some(
12358                            [(
12359                                params.text_document.uri.clone(),
12360                                vec![lsp::TextEdit::new(
12361                                    lsp::Range::new(
12362                                        lsp::Position::new(1, 0),
12363                                        lsp::Position::new(2, 0),
12364                                    ),
12365                                    "".to_string(),
12366                                )],
12367                            )]
12368                            .into_iter()
12369                            .collect(),
12370                        ),
12371                        ..Default::default()
12372                    }),
12373                    ..Default::default()
12374                },
12375            )]))
12376        })
12377        .next()
12378        .await;
12379    cx.executor().start_waiting();
12380    format.await;
12381    assert_eq!(
12382        editor.update(cx, |editor, cx| editor.text(cx)),
12383        "import { a } from 'module';\n\nconst x = a;\n"
12384    );
12385
12386    editor.update_in(cx, |editor, window, cx| {
12387        editor.set_text(
12388            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12389            window,
12390            cx,
12391        )
12392    });
12393    // Ensure we don't lock if code action hangs.
12394    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12395        move |params, _| async move {
12396            assert_eq!(
12397                params.text_document.uri,
12398                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12399            );
12400            futures::future::pending::<()>().await;
12401            unreachable!()
12402        },
12403    );
12404    let format = editor
12405        .update_in(cx, |editor, window, cx| {
12406            editor.perform_code_action_kind(
12407                project,
12408                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12409                window,
12410                cx,
12411            )
12412        })
12413        .unwrap();
12414    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12415    cx.executor().start_waiting();
12416    format.await;
12417    assert_eq!(
12418        editor.update(cx, |editor, cx| editor.text(cx)),
12419        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12420    );
12421}
12422
12423#[gpui::test]
12424async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12425    init_test(cx, |_| {});
12426
12427    let mut cx = EditorLspTestContext::new_rust(
12428        lsp::ServerCapabilities {
12429            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12430            ..Default::default()
12431        },
12432        cx,
12433    )
12434    .await;
12435
12436    cx.set_state(indoc! {"
12437        one.twoˇ
12438    "});
12439
12440    // The format request takes a long time. When it completes, it inserts
12441    // a newline and an indent before the `.`
12442    cx.lsp
12443        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12444            let executor = cx.background_executor().clone();
12445            async move {
12446                executor.timer(Duration::from_millis(100)).await;
12447                Ok(Some(vec![lsp::TextEdit {
12448                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12449                    new_text: "\n    ".into(),
12450                }]))
12451            }
12452        });
12453
12454    // Submit a format request.
12455    let format_1 = cx
12456        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12457        .unwrap();
12458    cx.executor().run_until_parked();
12459
12460    // Submit a second format request.
12461    let format_2 = cx
12462        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12463        .unwrap();
12464    cx.executor().run_until_parked();
12465
12466    // Wait for both format requests to complete
12467    cx.executor().advance_clock(Duration::from_millis(200));
12468    cx.executor().start_waiting();
12469    format_1.await.unwrap();
12470    cx.executor().start_waiting();
12471    format_2.await.unwrap();
12472
12473    // The formatting edits only happens once.
12474    cx.assert_editor_state(indoc! {"
12475        one
12476            .twoˇ
12477    "});
12478}
12479
12480#[gpui::test]
12481async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12482    init_test(cx, |settings| {
12483        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12484    });
12485
12486    let mut cx = EditorLspTestContext::new_rust(
12487        lsp::ServerCapabilities {
12488            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12489            ..Default::default()
12490        },
12491        cx,
12492    )
12493    .await;
12494
12495    // Set up a buffer white some trailing whitespace and no trailing newline.
12496    cx.set_state(
12497        &[
12498            "one ",   //
12499            "twoˇ",   //
12500            "three ", //
12501            "four",   //
12502        ]
12503        .join("\n"),
12504    );
12505
12506    // Record which buffer changes have been sent to the language server
12507    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12508    cx.lsp
12509        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12510            let buffer_changes = buffer_changes.clone();
12511            move |params, _| {
12512                buffer_changes.lock().extend(
12513                    params
12514                        .content_changes
12515                        .into_iter()
12516                        .map(|e| (e.range.unwrap(), e.text)),
12517                );
12518            }
12519        });
12520
12521    // Handle formatting requests to the language server.
12522    cx.lsp
12523        .set_request_handler::<lsp::request::Formatting, _, _>({
12524            let buffer_changes = buffer_changes.clone();
12525            move |_, _| {
12526                let buffer_changes = buffer_changes.clone();
12527                // Insert blank lines between each line of the buffer.
12528                async move {
12529                    // When formatting is requested, trailing whitespace has already been stripped,
12530                    // and the trailing newline has already been added.
12531                    assert_eq!(
12532                        &buffer_changes.lock()[1..],
12533                        &[
12534                            (
12535                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12536                                "".into()
12537                            ),
12538                            (
12539                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12540                                "".into()
12541                            ),
12542                            (
12543                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12544                                "\n".into()
12545                            ),
12546                        ]
12547                    );
12548
12549                    Ok(Some(vec![
12550                        lsp::TextEdit {
12551                            range: lsp::Range::new(
12552                                lsp::Position::new(1, 0),
12553                                lsp::Position::new(1, 0),
12554                            ),
12555                            new_text: "\n".into(),
12556                        },
12557                        lsp::TextEdit {
12558                            range: lsp::Range::new(
12559                                lsp::Position::new(2, 0),
12560                                lsp::Position::new(2, 0),
12561                            ),
12562                            new_text: "\n".into(),
12563                        },
12564                    ]))
12565                }
12566            }
12567        });
12568
12569    // Submit a format request.
12570    let format = cx
12571        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12572        .unwrap();
12573
12574    cx.run_until_parked();
12575    // After formatting the buffer, the trailing whitespace is stripped,
12576    // a newline is appended, and the edits provided by the language server
12577    // have been applied.
12578    format.await.unwrap();
12579
12580    cx.assert_editor_state(
12581        &[
12582            "one",   //
12583            "",      //
12584            "twoˇ",  //
12585            "",      //
12586            "three", //
12587            "four",  //
12588            "",      //
12589        ]
12590        .join("\n"),
12591    );
12592
12593    // Undoing the formatting undoes the trailing whitespace removal, the
12594    // trailing newline, and the LSP edits.
12595    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12596    cx.assert_editor_state(
12597        &[
12598            "one ",   //
12599            "twoˇ",   //
12600            "three ", //
12601            "four",   //
12602        ]
12603        .join("\n"),
12604    );
12605}
12606
12607#[gpui::test]
12608async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12609    cx: &mut TestAppContext,
12610) {
12611    init_test(cx, |_| {});
12612
12613    cx.update(|cx| {
12614        cx.update_global::<SettingsStore, _>(|settings, cx| {
12615            settings.update_user_settings(cx, |settings| {
12616                settings.editor.auto_signature_help = Some(true);
12617            });
12618        });
12619    });
12620
12621    let mut cx = EditorLspTestContext::new_rust(
12622        lsp::ServerCapabilities {
12623            signature_help_provider: Some(lsp::SignatureHelpOptions {
12624                ..Default::default()
12625            }),
12626            ..Default::default()
12627        },
12628        cx,
12629    )
12630    .await;
12631
12632    let language = Language::new(
12633        LanguageConfig {
12634            name: "Rust".into(),
12635            brackets: BracketPairConfig {
12636                pairs: vec![
12637                    BracketPair {
12638                        start: "{".to_string(),
12639                        end: "}".to_string(),
12640                        close: true,
12641                        surround: true,
12642                        newline: true,
12643                    },
12644                    BracketPair {
12645                        start: "(".to_string(),
12646                        end: ")".to_string(),
12647                        close: true,
12648                        surround: true,
12649                        newline: true,
12650                    },
12651                    BracketPair {
12652                        start: "/*".to_string(),
12653                        end: " */".to_string(),
12654                        close: true,
12655                        surround: true,
12656                        newline: true,
12657                    },
12658                    BracketPair {
12659                        start: "[".to_string(),
12660                        end: "]".to_string(),
12661                        close: false,
12662                        surround: false,
12663                        newline: true,
12664                    },
12665                    BracketPair {
12666                        start: "\"".to_string(),
12667                        end: "\"".to_string(),
12668                        close: true,
12669                        surround: true,
12670                        newline: false,
12671                    },
12672                    BracketPair {
12673                        start: "<".to_string(),
12674                        end: ">".to_string(),
12675                        close: false,
12676                        surround: true,
12677                        newline: true,
12678                    },
12679                ],
12680                ..Default::default()
12681            },
12682            autoclose_before: "})]".to_string(),
12683            ..Default::default()
12684        },
12685        Some(tree_sitter_rust::LANGUAGE.into()),
12686    );
12687    let language = Arc::new(language);
12688
12689    cx.language_registry().add(language.clone());
12690    cx.update_buffer(|buffer, cx| {
12691        buffer.set_language(Some(language), cx);
12692    });
12693
12694    cx.set_state(
12695        &r#"
12696            fn main() {
12697                sampleˇ
12698            }
12699        "#
12700        .unindent(),
12701    );
12702
12703    cx.update_editor(|editor, window, cx| {
12704        editor.handle_input("(", window, cx);
12705    });
12706    cx.assert_editor_state(
12707        &"
12708            fn main() {
12709                sample(ˇ)
12710            }
12711        "
12712        .unindent(),
12713    );
12714
12715    let mocked_response = lsp::SignatureHelp {
12716        signatures: vec![lsp::SignatureInformation {
12717            label: "fn sample(param1: u8, param2: u8)".to_string(),
12718            documentation: None,
12719            parameters: Some(vec![
12720                lsp::ParameterInformation {
12721                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12722                    documentation: None,
12723                },
12724                lsp::ParameterInformation {
12725                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12726                    documentation: None,
12727                },
12728            ]),
12729            active_parameter: None,
12730        }],
12731        active_signature: Some(0),
12732        active_parameter: Some(0),
12733    };
12734    handle_signature_help_request(&mut cx, mocked_response).await;
12735
12736    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12737        .await;
12738
12739    cx.editor(|editor, _, _| {
12740        let signature_help_state = editor.signature_help_state.popover().cloned();
12741        let signature = signature_help_state.unwrap();
12742        assert_eq!(
12743            signature.signatures[signature.current_signature].label,
12744            "fn sample(param1: u8, param2: u8)"
12745        );
12746    });
12747}
12748
12749#[gpui::test]
12750async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12751    init_test(cx, |_| {});
12752
12753    cx.update(|cx| {
12754        cx.update_global::<SettingsStore, _>(|settings, cx| {
12755            settings.update_user_settings(cx, |settings| {
12756                settings.editor.auto_signature_help = Some(false);
12757                settings.editor.show_signature_help_after_edits = Some(false);
12758            });
12759        });
12760    });
12761
12762    let mut cx = EditorLspTestContext::new_rust(
12763        lsp::ServerCapabilities {
12764            signature_help_provider: Some(lsp::SignatureHelpOptions {
12765                ..Default::default()
12766            }),
12767            ..Default::default()
12768        },
12769        cx,
12770    )
12771    .await;
12772
12773    let language = Language::new(
12774        LanguageConfig {
12775            name: "Rust".into(),
12776            brackets: BracketPairConfig {
12777                pairs: vec![
12778                    BracketPair {
12779                        start: "{".to_string(),
12780                        end: "}".to_string(),
12781                        close: true,
12782                        surround: true,
12783                        newline: true,
12784                    },
12785                    BracketPair {
12786                        start: "(".to_string(),
12787                        end: ")".to_string(),
12788                        close: true,
12789                        surround: true,
12790                        newline: true,
12791                    },
12792                    BracketPair {
12793                        start: "/*".to_string(),
12794                        end: " */".to_string(),
12795                        close: true,
12796                        surround: true,
12797                        newline: true,
12798                    },
12799                    BracketPair {
12800                        start: "[".to_string(),
12801                        end: "]".to_string(),
12802                        close: false,
12803                        surround: false,
12804                        newline: true,
12805                    },
12806                    BracketPair {
12807                        start: "\"".to_string(),
12808                        end: "\"".to_string(),
12809                        close: true,
12810                        surround: true,
12811                        newline: false,
12812                    },
12813                    BracketPair {
12814                        start: "<".to_string(),
12815                        end: ">".to_string(),
12816                        close: false,
12817                        surround: true,
12818                        newline: true,
12819                    },
12820                ],
12821                ..Default::default()
12822            },
12823            autoclose_before: "})]".to_string(),
12824            ..Default::default()
12825        },
12826        Some(tree_sitter_rust::LANGUAGE.into()),
12827    );
12828    let language = Arc::new(language);
12829
12830    cx.language_registry().add(language.clone());
12831    cx.update_buffer(|buffer, cx| {
12832        buffer.set_language(Some(language), cx);
12833    });
12834
12835    // Ensure that signature_help is not called when no signature help is enabled.
12836    cx.set_state(
12837        &r#"
12838            fn main() {
12839                sampleˇ
12840            }
12841        "#
12842        .unindent(),
12843    );
12844    cx.update_editor(|editor, window, cx| {
12845        editor.handle_input("(", window, cx);
12846    });
12847    cx.assert_editor_state(
12848        &"
12849            fn main() {
12850                sample(ˇ)
12851            }
12852        "
12853        .unindent(),
12854    );
12855    cx.editor(|editor, _, _| {
12856        assert!(editor.signature_help_state.task().is_none());
12857    });
12858
12859    let mocked_response = lsp::SignatureHelp {
12860        signatures: vec![lsp::SignatureInformation {
12861            label: "fn sample(param1: u8, param2: u8)".to_string(),
12862            documentation: None,
12863            parameters: Some(vec![
12864                lsp::ParameterInformation {
12865                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12866                    documentation: None,
12867                },
12868                lsp::ParameterInformation {
12869                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12870                    documentation: None,
12871                },
12872            ]),
12873            active_parameter: None,
12874        }],
12875        active_signature: Some(0),
12876        active_parameter: Some(0),
12877    };
12878
12879    // Ensure that signature_help is called when enabled afte edits
12880    cx.update(|_, cx| {
12881        cx.update_global::<SettingsStore, _>(|settings, cx| {
12882            settings.update_user_settings(cx, |settings| {
12883                settings.editor.auto_signature_help = Some(false);
12884                settings.editor.show_signature_help_after_edits = Some(true);
12885            });
12886        });
12887    });
12888    cx.set_state(
12889        &r#"
12890            fn main() {
12891                sampleˇ
12892            }
12893        "#
12894        .unindent(),
12895    );
12896    cx.update_editor(|editor, window, cx| {
12897        editor.handle_input("(", window, cx);
12898    });
12899    cx.assert_editor_state(
12900        &"
12901            fn main() {
12902                sample(ˇ)
12903            }
12904        "
12905        .unindent(),
12906    );
12907    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12908    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12909        .await;
12910    cx.update_editor(|editor, _, _| {
12911        let signature_help_state = editor.signature_help_state.popover().cloned();
12912        assert!(signature_help_state.is_some());
12913        let signature = signature_help_state.unwrap();
12914        assert_eq!(
12915            signature.signatures[signature.current_signature].label,
12916            "fn sample(param1: u8, param2: u8)"
12917        );
12918        editor.signature_help_state = SignatureHelpState::default();
12919    });
12920
12921    // Ensure that signature_help is called when auto signature help override is enabled
12922    cx.update(|_, cx| {
12923        cx.update_global::<SettingsStore, _>(|settings, cx| {
12924            settings.update_user_settings(cx, |settings| {
12925                settings.editor.auto_signature_help = Some(true);
12926                settings.editor.show_signature_help_after_edits = Some(false);
12927            });
12928        });
12929    });
12930    cx.set_state(
12931        &r#"
12932            fn main() {
12933                sampleˇ
12934            }
12935        "#
12936        .unindent(),
12937    );
12938    cx.update_editor(|editor, window, cx| {
12939        editor.handle_input("(", window, cx);
12940    });
12941    cx.assert_editor_state(
12942        &"
12943            fn main() {
12944                sample(ˇ)
12945            }
12946        "
12947        .unindent(),
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        let signature_help_state = editor.signature_help_state.popover().cloned();
12954        assert!(signature_help_state.is_some());
12955        let signature = signature_help_state.unwrap();
12956        assert_eq!(
12957            signature.signatures[signature.current_signature].label,
12958            "fn sample(param1: u8, param2: u8)"
12959        );
12960    });
12961}
12962
12963#[gpui::test]
12964async fn test_signature_help(cx: &mut TestAppContext) {
12965    init_test(cx, |_| {});
12966    cx.update(|cx| {
12967        cx.update_global::<SettingsStore, _>(|settings, cx| {
12968            settings.update_user_settings(cx, |settings| {
12969                settings.editor.auto_signature_help = Some(true);
12970            });
12971        });
12972    });
12973
12974    let mut cx = EditorLspTestContext::new_rust(
12975        lsp::ServerCapabilities {
12976            signature_help_provider: Some(lsp::SignatureHelpOptions {
12977                ..Default::default()
12978            }),
12979            ..Default::default()
12980        },
12981        cx,
12982    )
12983    .await;
12984
12985    // A test that directly calls `show_signature_help`
12986    cx.update_editor(|editor, window, cx| {
12987        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12988    });
12989
12990    let mocked_response = lsp::SignatureHelp {
12991        signatures: vec![lsp::SignatureInformation {
12992            label: "fn sample(param1: u8, param2: u8)".to_string(),
12993            documentation: None,
12994            parameters: Some(vec![
12995                lsp::ParameterInformation {
12996                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12997                    documentation: None,
12998                },
12999                lsp::ParameterInformation {
13000                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13001                    documentation: None,
13002                },
13003            ]),
13004            active_parameter: None,
13005        }],
13006        active_signature: Some(0),
13007        active_parameter: Some(0),
13008    };
13009    handle_signature_help_request(&mut cx, mocked_response).await;
13010
13011    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13012        .await;
13013
13014    cx.editor(|editor, _, _| {
13015        let signature_help_state = editor.signature_help_state.popover().cloned();
13016        assert!(signature_help_state.is_some());
13017        let signature = signature_help_state.unwrap();
13018        assert_eq!(
13019            signature.signatures[signature.current_signature].label,
13020            "fn sample(param1: u8, param2: u8)"
13021        );
13022    });
13023
13024    // When exiting outside from inside the brackets, `signature_help` is closed.
13025    cx.set_state(indoc! {"
13026        fn main() {
13027            sample(ˇ);
13028        }
13029
13030        fn sample(param1: u8, param2: u8) {}
13031    "});
13032
13033    cx.update_editor(|editor, window, cx| {
13034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13035            s.select_ranges([0..0])
13036        });
13037    });
13038
13039    let mocked_response = lsp::SignatureHelp {
13040        signatures: Vec::new(),
13041        active_signature: None,
13042        active_parameter: None,
13043    };
13044    handle_signature_help_request(&mut cx, mocked_response).await;
13045
13046    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13047        .await;
13048
13049    cx.editor(|editor, _, _| {
13050        assert!(!editor.signature_help_state.is_shown());
13051    });
13052
13053    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13054    cx.set_state(indoc! {"
13055        fn main() {
13056            sample(ˇ);
13057        }
13058
13059        fn sample(param1: u8, param2: u8) {}
13060    "});
13061
13062    let mocked_response = lsp::SignatureHelp {
13063        signatures: vec![lsp::SignatureInformation {
13064            label: "fn sample(param1: u8, param2: u8)".to_string(),
13065            documentation: None,
13066            parameters: Some(vec![
13067                lsp::ParameterInformation {
13068                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13069                    documentation: None,
13070                },
13071                lsp::ParameterInformation {
13072                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13073                    documentation: None,
13074                },
13075            ]),
13076            active_parameter: None,
13077        }],
13078        active_signature: Some(0),
13079        active_parameter: Some(0),
13080    };
13081    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13082    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13083        .await;
13084    cx.editor(|editor, _, _| {
13085        assert!(editor.signature_help_state.is_shown());
13086    });
13087
13088    // Restore the popover with more parameter input
13089    cx.set_state(indoc! {"
13090        fn main() {
13091            sample(param1, param2ˇ);
13092        }
13093
13094        fn sample(param1: u8, param2: u8) {}
13095    "});
13096
13097    let mocked_response = lsp::SignatureHelp {
13098        signatures: vec![lsp::SignatureInformation {
13099            label: "fn sample(param1: u8, param2: u8)".to_string(),
13100            documentation: None,
13101            parameters: Some(vec![
13102                lsp::ParameterInformation {
13103                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13104                    documentation: None,
13105                },
13106                lsp::ParameterInformation {
13107                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13108                    documentation: None,
13109                },
13110            ]),
13111            active_parameter: None,
13112        }],
13113        active_signature: Some(0),
13114        active_parameter: Some(1),
13115    };
13116    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13117    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13118        .await;
13119
13120    // When selecting a range, the popover is gone.
13121    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13122    cx.update_editor(|editor, window, cx| {
13123        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13124            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13125        })
13126    });
13127    cx.assert_editor_state(indoc! {"
13128        fn main() {
13129            sample(param1, «ˇparam2»);
13130        }
13131
13132        fn sample(param1: u8, param2: u8) {}
13133    "});
13134    cx.editor(|editor, _, _| {
13135        assert!(!editor.signature_help_state.is_shown());
13136    });
13137
13138    // When unselecting again, the popover is back if within the brackets.
13139    cx.update_editor(|editor, window, cx| {
13140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13141            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13142        })
13143    });
13144    cx.assert_editor_state(indoc! {"
13145        fn main() {
13146            sample(param1, ˇparam2);
13147        }
13148
13149        fn sample(param1: u8, param2: u8) {}
13150    "});
13151    handle_signature_help_request(&mut cx, mocked_response).await;
13152    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13153        .await;
13154    cx.editor(|editor, _, _| {
13155        assert!(editor.signature_help_state.is_shown());
13156    });
13157
13158    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13159    cx.update_editor(|editor, window, cx| {
13160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13161            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13162            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13163        })
13164    });
13165    cx.assert_editor_state(indoc! {"
13166        fn main() {
13167            sample(param1, ˇparam2);
13168        }
13169
13170        fn sample(param1: u8, param2: u8) {}
13171    "});
13172
13173    let mocked_response = lsp::SignatureHelp {
13174        signatures: vec![lsp::SignatureInformation {
13175            label: "fn sample(param1: u8, param2: u8)".to_string(),
13176            documentation: None,
13177            parameters: Some(vec![
13178                lsp::ParameterInformation {
13179                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13180                    documentation: None,
13181                },
13182                lsp::ParameterInformation {
13183                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13184                    documentation: None,
13185                },
13186            ]),
13187            active_parameter: None,
13188        }],
13189        active_signature: Some(0),
13190        active_parameter: Some(1),
13191    };
13192    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13193    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13194        .await;
13195    cx.update_editor(|editor, _, cx| {
13196        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13197    });
13198    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13199        .await;
13200    cx.update_editor(|editor, window, cx| {
13201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13202            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13203        })
13204    });
13205    cx.assert_editor_state(indoc! {"
13206        fn main() {
13207            sample(param1, «ˇparam2»);
13208        }
13209
13210        fn sample(param1: u8, param2: u8) {}
13211    "});
13212    cx.update_editor(|editor, window, cx| {
13213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13214            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13215        })
13216    });
13217    cx.assert_editor_state(indoc! {"
13218        fn main() {
13219            sample(param1, ˇparam2);
13220        }
13221
13222        fn sample(param1: u8, param2: u8) {}
13223    "});
13224    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13225        .await;
13226}
13227
13228#[gpui::test]
13229async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13230    init_test(cx, |_| {});
13231
13232    let mut cx = EditorLspTestContext::new_rust(
13233        lsp::ServerCapabilities {
13234            signature_help_provider: Some(lsp::SignatureHelpOptions {
13235                ..Default::default()
13236            }),
13237            ..Default::default()
13238        },
13239        cx,
13240    )
13241    .await;
13242
13243    cx.set_state(indoc! {"
13244        fn main() {
13245            overloadedˇ
13246        }
13247    "});
13248
13249    cx.update_editor(|editor, window, cx| {
13250        editor.handle_input("(", window, cx);
13251        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13252    });
13253
13254    // Mock response with 3 signatures
13255    let mocked_response = lsp::SignatureHelp {
13256        signatures: vec![
13257            lsp::SignatureInformation {
13258                label: "fn overloaded(x: i32)".to_string(),
13259                documentation: None,
13260                parameters: Some(vec![lsp::ParameterInformation {
13261                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13262                    documentation: None,
13263                }]),
13264                active_parameter: None,
13265            },
13266            lsp::SignatureInformation {
13267                label: "fn overloaded(x: i32, y: i32)".to_string(),
13268                documentation: None,
13269                parameters: Some(vec![
13270                    lsp::ParameterInformation {
13271                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13272                        documentation: None,
13273                    },
13274                    lsp::ParameterInformation {
13275                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13276                        documentation: None,
13277                    },
13278                ]),
13279                active_parameter: None,
13280            },
13281            lsp::SignatureInformation {
13282                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13283                documentation: None,
13284                parameters: Some(vec![
13285                    lsp::ParameterInformation {
13286                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13287                        documentation: None,
13288                    },
13289                    lsp::ParameterInformation {
13290                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13291                        documentation: None,
13292                    },
13293                    lsp::ParameterInformation {
13294                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13295                        documentation: None,
13296                    },
13297                ]),
13298                active_parameter: None,
13299            },
13300        ],
13301        active_signature: Some(1),
13302        active_parameter: Some(0),
13303    };
13304    handle_signature_help_request(&mut cx, mocked_response).await;
13305
13306    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13307        .await;
13308
13309    // Verify we have multiple signatures and the right one is selected
13310    cx.editor(|editor, _, _| {
13311        let popover = editor.signature_help_state.popover().cloned().unwrap();
13312        assert_eq!(popover.signatures.len(), 3);
13313        // active_signature was 1, so that should be the current
13314        assert_eq!(popover.current_signature, 1);
13315        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13316        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13317        assert_eq!(
13318            popover.signatures[2].label,
13319            "fn overloaded(x: i32, y: i32, z: i32)"
13320        );
13321    });
13322
13323    // Test navigation functionality
13324    cx.update_editor(|editor, window, cx| {
13325        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13326    });
13327
13328    cx.editor(|editor, _, _| {
13329        let popover = editor.signature_help_state.popover().cloned().unwrap();
13330        assert_eq!(popover.current_signature, 2);
13331    });
13332
13333    // Test wrap around
13334    cx.update_editor(|editor, window, cx| {
13335        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13336    });
13337
13338    cx.editor(|editor, _, _| {
13339        let popover = editor.signature_help_state.popover().cloned().unwrap();
13340        assert_eq!(popover.current_signature, 0);
13341    });
13342
13343    // Test previous navigation
13344    cx.update_editor(|editor, window, cx| {
13345        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13346    });
13347
13348    cx.editor(|editor, _, _| {
13349        let popover = editor.signature_help_state.popover().cloned().unwrap();
13350        assert_eq!(popover.current_signature, 2);
13351    });
13352}
13353
13354#[gpui::test]
13355async fn test_completion_mode(cx: &mut TestAppContext) {
13356    init_test(cx, |_| {});
13357    let mut cx = EditorLspTestContext::new_rust(
13358        lsp::ServerCapabilities {
13359            completion_provider: Some(lsp::CompletionOptions {
13360                resolve_provider: Some(true),
13361                ..Default::default()
13362            }),
13363            ..Default::default()
13364        },
13365        cx,
13366    )
13367    .await;
13368
13369    struct Run {
13370        run_description: &'static str,
13371        initial_state: String,
13372        buffer_marked_text: String,
13373        completion_label: &'static str,
13374        completion_text: &'static str,
13375        expected_with_insert_mode: String,
13376        expected_with_replace_mode: String,
13377        expected_with_replace_subsequence_mode: String,
13378        expected_with_replace_suffix_mode: String,
13379    }
13380
13381    let runs = [
13382        Run {
13383            run_description: "Start of word matches completion text",
13384            initial_state: "before ediˇ after".into(),
13385            buffer_marked_text: "before <edi|> after".into(),
13386            completion_label: "editor",
13387            completion_text: "editor",
13388            expected_with_insert_mode: "before editorˇ after".into(),
13389            expected_with_replace_mode: "before editorˇ after".into(),
13390            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13391            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13392        },
13393        Run {
13394            run_description: "Accept same text at the middle of the word",
13395            initial_state: "before ediˇtor after".into(),
13396            buffer_marked_text: "before <edi|tor> after".into(),
13397            completion_label: "editor",
13398            completion_text: "editor",
13399            expected_with_insert_mode: "before editorˇtor after".into(),
13400            expected_with_replace_mode: "before editorˇ after".into(),
13401            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13402            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13403        },
13404        Run {
13405            run_description: "End of word matches completion text -- cursor at end",
13406            initial_state: "before torˇ after".into(),
13407            buffer_marked_text: "before <tor|> after".into(),
13408            completion_label: "editor",
13409            completion_text: "editor",
13410            expected_with_insert_mode: "before editorˇ after".into(),
13411            expected_with_replace_mode: "before editorˇ after".into(),
13412            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13413            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13414        },
13415        Run {
13416            run_description: "End of word matches completion text -- cursor at start",
13417            initial_state: "before ˇtor after".into(),
13418            buffer_marked_text: "before <|tor> after".into(),
13419            completion_label: "editor",
13420            completion_text: "editor",
13421            expected_with_insert_mode: "before editorˇtor after".into(),
13422            expected_with_replace_mode: "before editorˇ after".into(),
13423            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13424            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13425        },
13426        Run {
13427            run_description: "Prepend text containing whitespace",
13428            initial_state: "pˇfield: bool".into(),
13429            buffer_marked_text: "<p|field>: bool".into(),
13430            completion_label: "pub ",
13431            completion_text: "pub ",
13432            expected_with_insert_mode: "pub ˇfield: bool".into(),
13433            expected_with_replace_mode: "pub ˇ: bool".into(),
13434            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13435            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13436        },
13437        Run {
13438            run_description: "Add element to start of list",
13439            initial_state: "[element_ˇelement_2]".into(),
13440            buffer_marked_text: "[<element_|element_2>]".into(),
13441            completion_label: "element_1",
13442            completion_text: "element_1",
13443            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13444            expected_with_replace_mode: "[element_1ˇ]".into(),
13445            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13446            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13447        },
13448        Run {
13449            run_description: "Add element to start of list -- first and second elements are equal",
13450            initial_state: "[elˇelement]".into(),
13451            buffer_marked_text: "[<el|element>]".into(),
13452            completion_label: "element",
13453            completion_text: "element",
13454            expected_with_insert_mode: "[elementˇelement]".into(),
13455            expected_with_replace_mode: "[elementˇ]".into(),
13456            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13457            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13458        },
13459        Run {
13460            run_description: "Ends with matching suffix",
13461            initial_state: "SubˇError".into(),
13462            buffer_marked_text: "<Sub|Error>".into(),
13463            completion_label: "SubscriptionError",
13464            completion_text: "SubscriptionError",
13465            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13466            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13467            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13468            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13469        },
13470        Run {
13471            run_description: "Suffix is a subsequence -- contiguous",
13472            initial_state: "SubˇErr".into(),
13473            buffer_marked_text: "<Sub|Err>".into(),
13474            completion_label: "SubscriptionError",
13475            completion_text: "SubscriptionError",
13476            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13477            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13478            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13479            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13480        },
13481        Run {
13482            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13483            initial_state: "Suˇscrirr".into(),
13484            buffer_marked_text: "<Su|scrirr>".into(),
13485            completion_label: "SubscriptionError",
13486            completion_text: "SubscriptionError",
13487            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13488            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13489            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13490            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13491        },
13492        Run {
13493            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13494            initial_state: "foo(indˇix)".into(),
13495            buffer_marked_text: "foo(<ind|ix>)".into(),
13496            completion_label: "node_index",
13497            completion_text: "node_index",
13498            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13499            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13500            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13501            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13502        },
13503        Run {
13504            run_description: "Replace range ends before cursor - should extend to cursor",
13505            initial_state: "before editˇo after".into(),
13506            buffer_marked_text: "before <{ed}>it|o after".into(),
13507            completion_label: "editor",
13508            completion_text: "editor",
13509            expected_with_insert_mode: "before editorˇo after".into(),
13510            expected_with_replace_mode: "before editorˇo after".into(),
13511            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13512            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13513        },
13514        Run {
13515            run_description: "Uses label for suffix matching",
13516            initial_state: "before ediˇtor after".into(),
13517            buffer_marked_text: "before <edi|tor> after".into(),
13518            completion_label: "editor",
13519            completion_text: "editor()",
13520            expected_with_insert_mode: "before editor()ˇtor after".into(),
13521            expected_with_replace_mode: "before editor()ˇ after".into(),
13522            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13523            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13524        },
13525        Run {
13526            run_description: "Case insensitive subsequence and suffix matching",
13527            initial_state: "before EDiˇtoR after".into(),
13528            buffer_marked_text: "before <EDi|toR> after".into(),
13529            completion_label: "editor",
13530            completion_text: "editor",
13531            expected_with_insert_mode: "before editorˇtoR after".into(),
13532            expected_with_replace_mode: "before editorˇ after".into(),
13533            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13534            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13535        },
13536    ];
13537
13538    for run in runs {
13539        let run_variations = [
13540            (LspInsertMode::Insert, run.expected_with_insert_mode),
13541            (LspInsertMode::Replace, run.expected_with_replace_mode),
13542            (
13543                LspInsertMode::ReplaceSubsequence,
13544                run.expected_with_replace_subsequence_mode,
13545            ),
13546            (
13547                LspInsertMode::ReplaceSuffix,
13548                run.expected_with_replace_suffix_mode,
13549            ),
13550        ];
13551
13552        for (lsp_insert_mode, expected_text) in run_variations {
13553            eprintln!(
13554                "run = {:?}, mode = {lsp_insert_mode:.?}",
13555                run.run_description,
13556            );
13557
13558            update_test_language_settings(&mut cx, |settings| {
13559                settings.defaults.completions = Some(CompletionSettingsContent {
13560                    lsp_insert_mode: Some(lsp_insert_mode),
13561                    words: Some(WordsCompletionMode::Disabled),
13562                    words_min_length: Some(0),
13563                    ..Default::default()
13564                });
13565            });
13566
13567            cx.set_state(&run.initial_state);
13568            cx.update_editor(|editor, window, cx| {
13569                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13570            });
13571
13572            let counter = Arc::new(AtomicUsize::new(0));
13573            handle_completion_request_with_insert_and_replace(
13574                &mut cx,
13575                &run.buffer_marked_text,
13576                vec![(run.completion_label, run.completion_text)],
13577                counter.clone(),
13578            )
13579            .await;
13580            cx.condition(|editor, _| editor.context_menu_visible())
13581                .await;
13582            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13583
13584            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13585                editor
13586                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13587                    .unwrap()
13588            });
13589            cx.assert_editor_state(&expected_text);
13590            handle_resolve_completion_request(&mut cx, None).await;
13591            apply_additional_edits.await.unwrap();
13592        }
13593    }
13594}
13595
13596#[gpui::test]
13597async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13598    init_test(cx, |_| {});
13599    let mut cx = EditorLspTestContext::new_rust(
13600        lsp::ServerCapabilities {
13601            completion_provider: Some(lsp::CompletionOptions {
13602                resolve_provider: Some(true),
13603                ..Default::default()
13604            }),
13605            ..Default::default()
13606        },
13607        cx,
13608    )
13609    .await;
13610
13611    let initial_state = "SubˇError";
13612    let buffer_marked_text = "<Sub|Error>";
13613    let completion_text = "SubscriptionError";
13614    let expected_with_insert_mode = "SubscriptionErrorˇError";
13615    let expected_with_replace_mode = "SubscriptionErrorˇ";
13616
13617    update_test_language_settings(&mut cx, |settings| {
13618        settings.defaults.completions = Some(CompletionSettingsContent {
13619            words: Some(WordsCompletionMode::Disabled),
13620            words_min_length: Some(0),
13621            // set the opposite here to ensure that the action is overriding the default behavior
13622            lsp_insert_mode: Some(LspInsertMode::Insert),
13623            ..Default::default()
13624        });
13625    });
13626
13627    cx.set_state(initial_state);
13628    cx.update_editor(|editor, window, cx| {
13629        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13630    });
13631
13632    let counter = Arc::new(AtomicUsize::new(0));
13633    handle_completion_request_with_insert_and_replace(
13634        &mut cx,
13635        buffer_marked_text,
13636        vec![(completion_text, completion_text)],
13637        counter.clone(),
13638    )
13639    .await;
13640    cx.condition(|editor, _| editor.context_menu_visible())
13641        .await;
13642    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13643
13644    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13645        editor
13646            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13647            .unwrap()
13648    });
13649    cx.assert_editor_state(expected_with_replace_mode);
13650    handle_resolve_completion_request(&mut cx, None).await;
13651    apply_additional_edits.await.unwrap();
13652
13653    update_test_language_settings(&mut cx, |settings| {
13654        settings.defaults.completions = Some(CompletionSettingsContent {
13655            words: Some(WordsCompletionMode::Disabled),
13656            words_min_length: Some(0),
13657            // set the opposite here to ensure that the action is overriding the default behavior
13658            lsp_insert_mode: Some(LspInsertMode::Replace),
13659            ..Default::default()
13660        });
13661    });
13662
13663    cx.set_state(initial_state);
13664    cx.update_editor(|editor, window, cx| {
13665        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13666    });
13667    handle_completion_request_with_insert_and_replace(
13668        &mut cx,
13669        buffer_marked_text,
13670        vec![(completion_text, completion_text)],
13671        counter.clone(),
13672    )
13673    .await;
13674    cx.condition(|editor, _| editor.context_menu_visible())
13675        .await;
13676    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13677
13678    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13679        editor
13680            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13681            .unwrap()
13682    });
13683    cx.assert_editor_state(expected_with_insert_mode);
13684    handle_resolve_completion_request(&mut cx, None).await;
13685    apply_additional_edits.await.unwrap();
13686}
13687
13688#[gpui::test]
13689async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13690    init_test(cx, |_| {});
13691    let mut cx = EditorLspTestContext::new_rust(
13692        lsp::ServerCapabilities {
13693            completion_provider: Some(lsp::CompletionOptions {
13694                resolve_provider: Some(true),
13695                ..Default::default()
13696            }),
13697            ..Default::default()
13698        },
13699        cx,
13700    )
13701    .await;
13702
13703    // scenario: surrounding text matches completion text
13704    let completion_text = "to_offset";
13705    let initial_state = indoc! {"
13706        1. buf.to_offˇsuffix
13707        2. buf.to_offˇsuf
13708        3. buf.to_offˇfix
13709        4. buf.to_offˇ
13710        5. into_offˇensive
13711        6. ˇsuffix
13712        7. let ˇ //
13713        8. aaˇzz
13714        9. buf.to_off«zzzzzˇ»suffix
13715        10. buf.«ˇzzzzz»suffix
13716        11. to_off«ˇzzzzz»
13717
13718        buf.to_offˇsuffix  // newest cursor
13719    "};
13720    let completion_marked_buffer = indoc! {"
13721        1. buf.to_offsuffix
13722        2. buf.to_offsuf
13723        3. buf.to_offfix
13724        4. buf.to_off
13725        5. into_offensive
13726        6. suffix
13727        7. let  //
13728        8. aazz
13729        9. buf.to_offzzzzzsuffix
13730        10. buf.zzzzzsuffix
13731        11. to_offzzzzz
13732
13733        buf.<to_off|suffix>  // newest cursor
13734    "};
13735    let expected = indoc! {"
13736        1. buf.to_offsetˇ
13737        2. buf.to_offsetˇsuf
13738        3. buf.to_offsetˇfix
13739        4. buf.to_offsetˇ
13740        5. into_offsetˇensive
13741        6. to_offsetˇsuffix
13742        7. let to_offsetˇ //
13743        8. aato_offsetˇzz
13744        9. buf.to_offsetˇ
13745        10. buf.to_offsetˇsuffix
13746        11. to_offsetˇ
13747
13748        buf.to_offsetˇ  // newest cursor
13749    "};
13750    cx.set_state(initial_state);
13751    cx.update_editor(|editor, window, cx| {
13752        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13753    });
13754    handle_completion_request_with_insert_and_replace(
13755        &mut cx,
13756        completion_marked_buffer,
13757        vec![(completion_text, completion_text)],
13758        Arc::new(AtomicUsize::new(0)),
13759    )
13760    .await;
13761    cx.condition(|editor, _| editor.context_menu_visible())
13762        .await;
13763    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13764        editor
13765            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13766            .unwrap()
13767    });
13768    cx.assert_editor_state(expected);
13769    handle_resolve_completion_request(&mut cx, None).await;
13770    apply_additional_edits.await.unwrap();
13771
13772    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13773    let completion_text = "foo_and_bar";
13774    let initial_state = indoc! {"
13775        1. ooanbˇ
13776        2. zooanbˇ
13777        3. ooanbˇz
13778        4. zooanbˇz
13779        5. ooanˇ
13780        6. oanbˇ
13781
13782        ooanbˇ
13783    "};
13784    let completion_marked_buffer = indoc! {"
13785        1. ooanb
13786        2. zooanb
13787        3. ooanbz
13788        4. zooanbz
13789        5. ooan
13790        6. oanb
13791
13792        <ooanb|>
13793    "};
13794    let expected = indoc! {"
13795        1. foo_and_barˇ
13796        2. zfoo_and_barˇ
13797        3. foo_and_barˇz
13798        4. zfoo_and_barˇz
13799        5. ooanfoo_and_barˇ
13800        6. oanbfoo_and_barˇ
13801
13802        foo_and_barˇ
13803    "};
13804    cx.set_state(initial_state);
13805    cx.update_editor(|editor, window, cx| {
13806        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13807    });
13808    handle_completion_request_with_insert_and_replace(
13809        &mut cx,
13810        completion_marked_buffer,
13811        vec![(completion_text, completion_text)],
13812        Arc::new(AtomicUsize::new(0)),
13813    )
13814    .await;
13815    cx.condition(|editor, _| editor.context_menu_visible())
13816        .await;
13817    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13818        editor
13819            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13820            .unwrap()
13821    });
13822    cx.assert_editor_state(expected);
13823    handle_resolve_completion_request(&mut cx, None).await;
13824    apply_additional_edits.await.unwrap();
13825
13826    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13827    // (expects the same as if it was inserted at the end)
13828    let completion_text = "foo_and_bar";
13829    let initial_state = indoc! {"
13830        1. ooˇanb
13831        2. zooˇanb
13832        3. ooˇanbz
13833        4. zooˇanbz
13834
13835        ooˇanb
13836    "};
13837    let completion_marked_buffer = indoc! {"
13838        1. ooanb
13839        2. zooanb
13840        3. ooanbz
13841        4. zooanbz
13842
13843        <oo|anb>
13844    "};
13845    let expected = indoc! {"
13846        1. foo_and_barˇ
13847        2. zfoo_and_barˇ
13848        3. foo_and_barˇz
13849        4. zfoo_and_barˇz
13850
13851        foo_and_barˇ
13852    "};
13853    cx.set_state(initial_state);
13854    cx.update_editor(|editor, window, cx| {
13855        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13856    });
13857    handle_completion_request_with_insert_and_replace(
13858        &mut cx,
13859        completion_marked_buffer,
13860        vec![(completion_text, completion_text)],
13861        Arc::new(AtomicUsize::new(0)),
13862    )
13863    .await;
13864    cx.condition(|editor, _| editor.context_menu_visible())
13865        .await;
13866    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13867        editor
13868            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13869            .unwrap()
13870    });
13871    cx.assert_editor_state(expected);
13872    handle_resolve_completion_request(&mut cx, None).await;
13873    apply_additional_edits.await.unwrap();
13874}
13875
13876// This used to crash
13877#[gpui::test]
13878async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13879    init_test(cx, |_| {});
13880
13881    let buffer_text = indoc! {"
13882        fn main() {
13883            10.satu;
13884
13885            //
13886            // separate cursors so they open in different excerpts (manually reproducible)
13887            //
13888
13889            10.satu20;
13890        }
13891    "};
13892    let multibuffer_text_with_selections = indoc! {"
13893        fn main() {
13894            10.satuˇ;
13895
13896            //
13897
13898            //
13899
13900            10.satuˇ20;
13901        }
13902    "};
13903    let expected_multibuffer = indoc! {"
13904        fn main() {
13905            10.saturating_sub()ˇ;
13906
13907            //
13908
13909            //
13910
13911            10.saturating_sub()ˇ;
13912        }
13913    "};
13914
13915    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13916    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13917
13918    let fs = FakeFs::new(cx.executor());
13919    fs.insert_tree(
13920        path!("/a"),
13921        json!({
13922            "main.rs": buffer_text,
13923        }),
13924    )
13925    .await;
13926
13927    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13928    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13929    language_registry.add(rust_lang());
13930    let mut fake_servers = language_registry.register_fake_lsp(
13931        "Rust",
13932        FakeLspAdapter {
13933            capabilities: lsp::ServerCapabilities {
13934                completion_provider: Some(lsp::CompletionOptions {
13935                    resolve_provider: None,
13936                    ..lsp::CompletionOptions::default()
13937                }),
13938                ..lsp::ServerCapabilities::default()
13939            },
13940            ..FakeLspAdapter::default()
13941        },
13942    );
13943    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13944    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13945    let buffer = project
13946        .update(cx, |project, cx| {
13947            project.open_local_buffer(path!("/a/main.rs"), cx)
13948        })
13949        .await
13950        .unwrap();
13951
13952    let multi_buffer = cx.new(|cx| {
13953        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13954        multi_buffer.push_excerpts(
13955            buffer.clone(),
13956            [ExcerptRange::new(0..first_excerpt_end)],
13957            cx,
13958        );
13959        multi_buffer.push_excerpts(
13960            buffer.clone(),
13961            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13962            cx,
13963        );
13964        multi_buffer
13965    });
13966
13967    let editor = workspace
13968        .update(cx, |_, window, cx| {
13969            cx.new(|cx| {
13970                Editor::new(
13971                    EditorMode::Full {
13972                        scale_ui_elements_with_buffer_font_size: false,
13973                        show_active_line_background: false,
13974                        sized_by_content: false,
13975                    },
13976                    multi_buffer.clone(),
13977                    Some(project.clone()),
13978                    window,
13979                    cx,
13980                )
13981            })
13982        })
13983        .unwrap();
13984
13985    let pane = workspace
13986        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13987        .unwrap();
13988    pane.update_in(cx, |pane, window, cx| {
13989        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13990    });
13991
13992    let fake_server = fake_servers.next().await.unwrap();
13993
13994    editor.update_in(cx, |editor, window, cx| {
13995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13996            s.select_ranges([
13997                Point::new(1, 11)..Point::new(1, 11),
13998                Point::new(7, 11)..Point::new(7, 11),
13999            ])
14000        });
14001
14002        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14003    });
14004
14005    editor.update_in(cx, |editor, window, cx| {
14006        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14007    });
14008
14009    fake_server
14010        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14011            let completion_item = lsp::CompletionItem {
14012                label: "saturating_sub()".into(),
14013                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14014                    lsp::InsertReplaceEdit {
14015                        new_text: "saturating_sub()".to_owned(),
14016                        insert: lsp::Range::new(
14017                            lsp::Position::new(7, 7),
14018                            lsp::Position::new(7, 11),
14019                        ),
14020                        replace: lsp::Range::new(
14021                            lsp::Position::new(7, 7),
14022                            lsp::Position::new(7, 13),
14023                        ),
14024                    },
14025                )),
14026                ..lsp::CompletionItem::default()
14027            };
14028
14029            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14030        })
14031        .next()
14032        .await
14033        .unwrap();
14034
14035    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14036        .await;
14037
14038    editor
14039        .update_in(cx, |editor, window, cx| {
14040            editor
14041                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14042                .unwrap()
14043        })
14044        .await
14045        .unwrap();
14046
14047    editor.update(cx, |editor, cx| {
14048        assert_text_with_selections(editor, expected_multibuffer, cx);
14049    })
14050}
14051
14052#[gpui::test]
14053async fn test_completion(cx: &mut TestAppContext) {
14054    init_test(cx, |_| {});
14055
14056    let mut cx = EditorLspTestContext::new_rust(
14057        lsp::ServerCapabilities {
14058            completion_provider: Some(lsp::CompletionOptions {
14059                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14060                resolve_provider: Some(true),
14061                ..Default::default()
14062            }),
14063            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14064            ..Default::default()
14065        },
14066        cx,
14067    )
14068    .await;
14069    let counter = Arc::new(AtomicUsize::new(0));
14070
14071    cx.set_state(indoc! {"
14072        oneˇ
14073        two
14074        three
14075    "});
14076    cx.simulate_keystroke(".");
14077    handle_completion_request(
14078        indoc! {"
14079            one.|<>
14080            two
14081            three
14082        "},
14083        vec!["first_completion", "second_completion"],
14084        true,
14085        counter.clone(),
14086        &mut cx,
14087    )
14088    .await;
14089    cx.condition(|editor, _| editor.context_menu_visible())
14090        .await;
14091    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14092
14093    let _handler = handle_signature_help_request(
14094        &mut cx,
14095        lsp::SignatureHelp {
14096            signatures: vec![lsp::SignatureInformation {
14097                label: "test signature".to_string(),
14098                documentation: None,
14099                parameters: Some(vec![lsp::ParameterInformation {
14100                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14101                    documentation: None,
14102                }]),
14103                active_parameter: None,
14104            }],
14105            active_signature: None,
14106            active_parameter: None,
14107        },
14108    );
14109    cx.update_editor(|editor, window, cx| {
14110        assert!(
14111            !editor.signature_help_state.is_shown(),
14112            "No signature help was called for"
14113        );
14114        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14115    });
14116    cx.run_until_parked();
14117    cx.update_editor(|editor, _, _| {
14118        assert!(
14119            !editor.signature_help_state.is_shown(),
14120            "No signature help should be shown when completions menu is open"
14121        );
14122    });
14123
14124    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14125        editor.context_menu_next(&Default::default(), window, cx);
14126        editor
14127            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14128            .unwrap()
14129    });
14130    cx.assert_editor_state(indoc! {"
14131        one.second_completionˇ
14132        two
14133        three
14134    "});
14135
14136    handle_resolve_completion_request(
14137        &mut cx,
14138        Some(vec![
14139            (
14140                //This overlaps with the primary completion edit which is
14141                //misbehavior from the LSP spec, test that we filter it out
14142                indoc! {"
14143                    one.second_ˇcompletion
14144                    two
14145                    threeˇ
14146                "},
14147                "overlapping additional edit",
14148            ),
14149            (
14150                indoc! {"
14151                    one.second_completion
14152                    two
14153                    threeˇ
14154                "},
14155                "\nadditional edit",
14156            ),
14157        ]),
14158    )
14159    .await;
14160    apply_additional_edits.await.unwrap();
14161    cx.assert_editor_state(indoc! {"
14162        one.second_completionˇ
14163        two
14164        three
14165        additional edit
14166    "});
14167
14168    cx.set_state(indoc! {"
14169        one.second_completion
14170        twoˇ
14171        threeˇ
14172        additional edit
14173    "});
14174    cx.simulate_keystroke(" ");
14175    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14176    cx.simulate_keystroke("s");
14177    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14178
14179    cx.assert_editor_state(indoc! {"
14180        one.second_completion
14181        two sˇ
14182        three sˇ
14183        additional edit
14184    "});
14185    handle_completion_request(
14186        indoc! {"
14187            one.second_completion
14188            two s
14189            three <s|>
14190            additional edit
14191        "},
14192        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14193        true,
14194        counter.clone(),
14195        &mut cx,
14196    )
14197    .await;
14198    cx.condition(|editor, _| editor.context_menu_visible())
14199        .await;
14200    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14201
14202    cx.simulate_keystroke("i");
14203
14204    handle_completion_request(
14205        indoc! {"
14206            one.second_completion
14207            two si
14208            three <si|>
14209            additional edit
14210        "},
14211        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14212        true,
14213        counter.clone(),
14214        &mut cx,
14215    )
14216    .await;
14217    cx.condition(|editor, _| editor.context_menu_visible())
14218        .await;
14219    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14220
14221    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14222        editor
14223            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14224            .unwrap()
14225    });
14226    cx.assert_editor_state(indoc! {"
14227        one.second_completion
14228        two sixth_completionˇ
14229        three sixth_completionˇ
14230        additional edit
14231    "});
14232
14233    apply_additional_edits.await.unwrap();
14234
14235    update_test_language_settings(&mut cx, |settings| {
14236        settings.defaults.show_completions_on_input = Some(false);
14237    });
14238    cx.set_state("editorˇ");
14239    cx.simulate_keystroke(".");
14240    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14241    cx.simulate_keystrokes("c l o");
14242    cx.assert_editor_state("editor.cloˇ");
14243    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14244    cx.update_editor(|editor, window, cx| {
14245        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14246    });
14247    handle_completion_request(
14248        "editor.<clo|>",
14249        vec!["close", "clobber"],
14250        true,
14251        counter.clone(),
14252        &mut cx,
14253    )
14254    .await;
14255    cx.condition(|editor, _| editor.context_menu_visible())
14256        .await;
14257    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14258
14259    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14260        editor
14261            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14262            .unwrap()
14263    });
14264    cx.assert_editor_state("editor.clobberˇ");
14265    handle_resolve_completion_request(&mut cx, None).await;
14266    apply_additional_edits.await.unwrap();
14267}
14268
14269#[gpui::test]
14270async fn test_completion_reuse(cx: &mut TestAppContext) {
14271    init_test(cx, |_| {});
14272
14273    let mut cx = EditorLspTestContext::new_rust(
14274        lsp::ServerCapabilities {
14275            completion_provider: Some(lsp::CompletionOptions {
14276                trigger_characters: Some(vec![".".to_string()]),
14277                ..Default::default()
14278            }),
14279            ..Default::default()
14280        },
14281        cx,
14282    )
14283    .await;
14284
14285    let counter = Arc::new(AtomicUsize::new(0));
14286    cx.set_state("objˇ");
14287    cx.simulate_keystroke(".");
14288
14289    // Initial completion request returns complete results
14290    let is_incomplete = false;
14291    handle_completion_request(
14292        "obj.|<>",
14293        vec!["a", "ab", "abc"],
14294        is_incomplete,
14295        counter.clone(),
14296        &mut cx,
14297    )
14298    .await;
14299    cx.run_until_parked();
14300    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14301    cx.assert_editor_state("obj.ˇ");
14302    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14303
14304    // Type "a" - filters existing completions
14305    cx.simulate_keystroke("a");
14306    cx.run_until_parked();
14307    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14308    cx.assert_editor_state("obj.aˇ");
14309    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14310
14311    // Type "b" - filters existing completions
14312    cx.simulate_keystroke("b");
14313    cx.run_until_parked();
14314    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14315    cx.assert_editor_state("obj.abˇ");
14316    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14317
14318    // Type "c" - filters existing completions
14319    cx.simulate_keystroke("c");
14320    cx.run_until_parked();
14321    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14322    cx.assert_editor_state("obj.abcˇ");
14323    check_displayed_completions(vec!["abc"], &mut cx);
14324
14325    // Backspace to delete "c" - filters existing completions
14326    cx.update_editor(|editor, window, cx| {
14327        editor.backspace(&Backspace, window, cx);
14328    });
14329    cx.run_until_parked();
14330    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14331    cx.assert_editor_state("obj.abˇ");
14332    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14333
14334    // Moving cursor to the left dismisses menu.
14335    cx.update_editor(|editor, window, cx| {
14336        editor.move_left(&MoveLeft, window, cx);
14337    });
14338    cx.run_until_parked();
14339    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14340    cx.assert_editor_state("obj.aˇb");
14341    cx.update_editor(|editor, _, _| {
14342        assert_eq!(editor.context_menu_visible(), false);
14343    });
14344
14345    // Type "b" - new request
14346    cx.simulate_keystroke("b");
14347    let is_incomplete = false;
14348    handle_completion_request(
14349        "obj.<ab|>a",
14350        vec!["ab", "abc"],
14351        is_incomplete,
14352        counter.clone(),
14353        &mut cx,
14354    )
14355    .await;
14356    cx.run_until_parked();
14357    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14358    cx.assert_editor_state("obj.abˇb");
14359    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14360
14361    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14362    cx.update_editor(|editor, window, cx| {
14363        editor.backspace(&Backspace, window, cx);
14364    });
14365    let is_incomplete = false;
14366    handle_completion_request(
14367        "obj.<a|>b",
14368        vec!["a", "ab", "abc"],
14369        is_incomplete,
14370        counter.clone(),
14371        &mut cx,
14372    )
14373    .await;
14374    cx.run_until_parked();
14375    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14376    cx.assert_editor_state("obj.aˇb");
14377    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14378
14379    // Backspace to delete "a" - dismisses menu.
14380    cx.update_editor(|editor, window, cx| {
14381        editor.backspace(&Backspace, window, cx);
14382    });
14383    cx.run_until_parked();
14384    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14385    cx.assert_editor_state("obj.ˇb");
14386    cx.update_editor(|editor, _, _| {
14387        assert_eq!(editor.context_menu_visible(), false);
14388    });
14389}
14390
14391#[gpui::test]
14392async fn test_word_completion(cx: &mut TestAppContext) {
14393    let lsp_fetch_timeout_ms = 10;
14394    init_test(cx, |language_settings| {
14395        language_settings.defaults.completions = Some(CompletionSettingsContent {
14396            words_min_length: Some(0),
14397            lsp_fetch_timeout_ms: Some(10),
14398            lsp_insert_mode: Some(LspInsertMode::Insert),
14399            ..Default::default()
14400        });
14401    });
14402
14403    let mut cx = EditorLspTestContext::new_rust(
14404        lsp::ServerCapabilities {
14405            completion_provider: Some(lsp::CompletionOptions {
14406                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14407                ..lsp::CompletionOptions::default()
14408            }),
14409            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14410            ..lsp::ServerCapabilities::default()
14411        },
14412        cx,
14413    )
14414    .await;
14415
14416    let throttle_completions = Arc::new(AtomicBool::new(false));
14417
14418    let lsp_throttle_completions = throttle_completions.clone();
14419    let _completion_requests_handler =
14420        cx.lsp
14421            .server
14422            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14423                let lsp_throttle_completions = lsp_throttle_completions.clone();
14424                let cx = cx.clone();
14425                async move {
14426                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14427                        cx.background_executor()
14428                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14429                            .await;
14430                    }
14431                    Ok(Some(lsp::CompletionResponse::Array(vec![
14432                        lsp::CompletionItem {
14433                            label: "first".into(),
14434                            ..lsp::CompletionItem::default()
14435                        },
14436                        lsp::CompletionItem {
14437                            label: "last".into(),
14438                            ..lsp::CompletionItem::default()
14439                        },
14440                    ])))
14441                }
14442            });
14443
14444    cx.set_state(indoc! {"
14445        oneˇ
14446        two
14447        three
14448    "});
14449    cx.simulate_keystroke(".");
14450    cx.executor().run_until_parked();
14451    cx.condition(|editor, _| editor.context_menu_visible())
14452        .await;
14453    cx.update_editor(|editor, window, cx| {
14454        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14455        {
14456            assert_eq!(
14457                completion_menu_entries(menu),
14458                &["first", "last"],
14459                "When LSP server is fast to reply, no fallback word completions are used"
14460            );
14461        } else {
14462            panic!("expected completion menu to be open");
14463        }
14464        editor.cancel(&Cancel, window, cx);
14465    });
14466    cx.executor().run_until_parked();
14467    cx.condition(|editor, _| !editor.context_menu_visible())
14468        .await;
14469
14470    throttle_completions.store(true, atomic::Ordering::Release);
14471    cx.simulate_keystroke(".");
14472    cx.executor()
14473        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14474    cx.executor().run_until_parked();
14475    cx.condition(|editor, _| editor.context_menu_visible())
14476        .await;
14477    cx.update_editor(|editor, _, _| {
14478        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14479        {
14480            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14481                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14482        } else {
14483            panic!("expected completion menu to be open");
14484        }
14485    });
14486}
14487
14488#[gpui::test]
14489async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14490    init_test(cx, |language_settings| {
14491        language_settings.defaults.completions = Some(CompletionSettingsContent {
14492            words: Some(WordsCompletionMode::Enabled),
14493            words_min_length: Some(0),
14494            lsp_insert_mode: Some(LspInsertMode::Insert),
14495            ..Default::default()
14496        });
14497    });
14498
14499    let mut cx = EditorLspTestContext::new_rust(
14500        lsp::ServerCapabilities {
14501            completion_provider: Some(lsp::CompletionOptions {
14502                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14503                ..lsp::CompletionOptions::default()
14504            }),
14505            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14506            ..lsp::ServerCapabilities::default()
14507        },
14508        cx,
14509    )
14510    .await;
14511
14512    let _completion_requests_handler =
14513        cx.lsp
14514            .server
14515            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14516                Ok(Some(lsp::CompletionResponse::Array(vec![
14517                    lsp::CompletionItem {
14518                        label: "first".into(),
14519                        ..lsp::CompletionItem::default()
14520                    },
14521                    lsp::CompletionItem {
14522                        label: "last".into(),
14523                        ..lsp::CompletionItem::default()
14524                    },
14525                ])))
14526            });
14527
14528    cx.set_state(indoc! {"ˇ
14529        first
14530        last
14531        second
14532    "});
14533    cx.simulate_keystroke(".");
14534    cx.executor().run_until_parked();
14535    cx.condition(|editor, _| editor.context_menu_visible())
14536        .await;
14537    cx.update_editor(|editor, _, _| {
14538        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14539        {
14540            assert_eq!(
14541                completion_menu_entries(menu),
14542                &["first", "last", "second"],
14543                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14544            );
14545        } else {
14546            panic!("expected completion menu to be open");
14547        }
14548    });
14549}
14550
14551#[gpui::test]
14552async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14553    init_test(cx, |language_settings| {
14554        language_settings.defaults.completions = Some(CompletionSettingsContent {
14555            words: Some(WordsCompletionMode::Disabled),
14556            words_min_length: Some(0),
14557            lsp_insert_mode: Some(LspInsertMode::Insert),
14558            ..Default::default()
14559        });
14560    });
14561
14562    let mut cx = EditorLspTestContext::new_rust(
14563        lsp::ServerCapabilities {
14564            completion_provider: Some(lsp::CompletionOptions {
14565                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14566                ..lsp::CompletionOptions::default()
14567            }),
14568            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14569            ..lsp::ServerCapabilities::default()
14570        },
14571        cx,
14572    )
14573    .await;
14574
14575    let _completion_requests_handler =
14576        cx.lsp
14577            .server
14578            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14579                panic!("LSP completions should not be queried when dealing with word completions")
14580            });
14581
14582    cx.set_state(indoc! {"ˇ
14583        first
14584        last
14585        second
14586    "});
14587    cx.update_editor(|editor, window, cx| {
14588        editor.show_word_completions(&ShowWordCompletions, window, cx);
14589    });
14590    cx.executor().run_until_parked();
14591    cx.condition(|editor, _| editor.context_menu_visible())
14592        .await;
14593    cx.update_editor(|editor, _, _| {
14594        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14595        {
14596            assert_eq!(
14597                completion_menu_entries(menu),
14598                &["first", "last", "second"],
14599                "`ShowWordCompletions` action should show word completions"
14600            );
14601        } else {
14602            panic!("expected completion menu to be open");
14603        }
14604    });
14605
14606    cx.simulate_keystroke("l");
14607    cx.executor().run_until_parked();
14608    cx.condition(|editor, _| editor.context_menu_visible())
14609        .await;
14610    cx.update_editor(|editor, _, _| {
14611        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14612        {
14613            assert_eq!(
14614                completion_menu_entries(menu),
14615                &["last"],
14616                "After showing word completions, further editing should filter them and not query the LSP"
14617            );
14618        } else {
14619            panic!("expected completion menu to be open");
14620        }
14621    });
14622}
14623
14624#[gpui::test]
14625async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14626    init_test(cx, |language_settings| {
14627        language_settings.defaults.completions = Some(CompletionSettingsContent {
14628            words_min_length: Some(0),
14629            lsp: Some(false),
14630            lsp_insert_mode: Some(LspInsertMode::Insert),
14631            ..Default::default()
14632        });
14633    });
14634
14635    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14636
14637    cx.set_state(indoc! {"ˇ
14638        0_usize
14639        let
14640        33
14641        4.5f32
14642    "});
14643    cx.update_editor(|editor, window, cx| {
14644        editor.show_completions(&ShowCompletions::default(), window, cx);
14645    });
14646    cx.executor().run_until_parked();
14647    cx.condition(|editor, _| editor.context_menu_visible())
14648        .await;
14649    cx.update_editor(|editor, window, cx| {
14650        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14651        {
14652            assert_eq!(
14653                completion_menu_entries(menu),
14654                &["let"],
14655                "With no digits in the completion query, no digits should be in the word completions"
14656            );
14657        } else {
14658            panic!("expected completion menu to be open");
14659        }
14660        editor.cancel(&Cancel, window, cx);
14661    });
14662
14663    cx.set_state(indoc! {"14664        0_usize
14665        let
14666        3
14667        33.35f32
14668    "});
14669    cx.update_editor(|editor, window, cx| {
14670        editor.show_completions(&ShowCompletions::default(), window, cx);
14671    });
14672    cx.executor().run_until_parked();
14673    cx.condition(|editor, _| editor.context_menu_visible())
14674        .await;
14675    cx.update_editor(|editor, _, _| {
14676        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14677        {
14678            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14679                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14680        } else {
14681            panic!("expected completion menu to be open");
14682        }
14683    });
14684}
14685
14686#[gpui::test]
14687async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14688    init_test(cx, |language_settings| {
14689        language_settings.defaults.completions = Some(CompletionSettingsContent {
14690            words: Some(WordsCompletionMode::Enabled),
14691            words_min_length: Some(3),
14692            lsp_insert_mode: Some(LspInsertMode::Insert),
14693            ..Default::default()
14694        });
14695    });
14696
14697    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14698    cx.set_state(indoc! {"ˇ
14699        wow
14700        wowen
14701        wowser
14702    "});
14703    cx.simulate_keystroke("w");
14704    cx.executor().run_until_parked();
14705    cx.update_editor(|editor, _, _| {
14706        if editor.context_menu.borrow_mut().is_some() {
14707            panic!(
14708                "expected completion menu to be hidden, as words completion threshold is not met"
14709            );
14710        }
14711    });
14712
14713    cx.update_editor(|editor, window, cx| {
14714        editor.show_word_completions(&ShowWordCompletions, window, cx);
14715    });
14716    cx.executor().run_until_parked();
14717    cx.update_editor(|editor, window, cx| {
14718        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14719        {
14720            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");
14721        } else {
14722            panic!("expected completion menu to be open after the word completions are called with an action");
14723        }
14724
14725        editor.cancel(&Cancel, window, cx);
14726    });
14727    cx.update_editor(|editor, _, _| {
14728        if editor.context_menu.borrow_mut().is_some() {
14729            panic!("expected completion menu to be hidden after canceling");
14730        }
14731    });
14732
14733    cx.simulate_keystroke("o");
14734    cx.executor().run_until_parked();
14735    cx.update_editor(|editor, _, _| {
14736        if editor.context_menu.borrow_mut().is_some() {
14737            panic!(
14738                "expected completion menu to be hidden, as words completion threshold is not met still"
14739            );
14740        }
14741    });
14742
14743    cx.simulate_keystroke("w");
14744    cx.executor().run_until_parked();
14745    cx.update_editor(|editor, _, _| {
14746        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14747        {
14748            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14749        } else {
14750            panic!("expected completion menu to be open after the word completions threshold is met");
14751        }
14752    });
14753}
14754
14755#[gpui::test]
14756async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14757    init_test(cx, |language_settings| {
14758        language_settings.defaults.completions = Some(CompletionSettingsContent {
14759            words: Some(WordsCompletionMode::Enabled),
14760            words_min_length: Some(0),
14761            lsp_insert_mode: Some(LspInsertMode::Insert),
14762            ..Default::default()
14763        });
14764    });
14765
14766    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14767    cx.update_editor(|editor, _, _| {
14768        editor.disable_word_completions();
14769    });
14770    cx.set_state(indoc! {"ˇ
14771        wow
14772        wowen
14773        wowser
14774    "});
14775    cx.simulate_keystroke("w");
14776    cx.executor().run_until_parked();
14777    cx.update_editor(|editor, _, _| {
14778        if editor.context_menu.borrow_mut().is_some() {
14779            panic!(
14780                "expected completion menu to be hidden, as words completion are disabled for this editor"
14781            );
14782        }
14783    });
14784
14785    cx.update_editor(|editor, window, cx| {
14786        editor.show_word_completions(&ShowWordCompletions, window, cx);
14787    });
14788    cx.executor().run_until_parked();
14789    cx.update_editor(|editor, _, _| {
14790        if editor.context_menu.borrow_mut().is_some() {
14791            panic!(
14792                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14793            );
14794        }
14795    });
14796}
14797
14798fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14799    let position = || lsp::Position {
14800        line: params.text_document_position.position.line,
14801        character: params.text_document_position.position.character,
14802    };
14803    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14804        range: lsp::Range {
14805            start: position(),
14806            end: position(),
14807        },
14808        new_text: text.to_string(),
14809    }))
14810}
14811
14812#[gpui::test]
14813async fn test_multiline_completion(cx: &mut TestAppContext) {
14814    init_test(cx, |_| {});
14815
14816    let fs = FakeFs::new(cx.executor());
14817    fs.insert_tree(
14818        path!("/a"),
14819        json!({
14820            "main.ts": "a",
14821        }),
14822    )
14823    .await;
14824
14825    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14826    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14827    let typescript_language = Arc::new(Language::new(
14828        LanguageConfig {
14829            name: "TypeScript".into(),
14830            matcher: LanguageMatcher {
14831                path_suffixes: vec!["ts".to_string()],
14832                ..LanguageMatcher::default()
14833            },
14834            line_comments: vec!["// ".into()],
14835            ..LanguageConfig::default()
14836        },
14837        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14838    ));
14839    language_registry.add(typescript_language.clone());
14840    let mut fake_servers = language_registry.register_fake_lsp(
14841        "TypeScript",
14842        FakeLspAdapter {
14843            capabilities: lsp::ServerCapabilities {
14844                completion_provider: Some(lsp::CompletionOptions {
14845                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14846                    ..lsp::CompletionOptions::default()
14847                }),
14848                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14849                ..lsp::ServerCapabilities::default()
14850            },
14851            // Emulate vtsls label generation
14852            label_for_completion: Some(Box::new(|item, _| {
14853                let text = if let Some(description) = item
14854                    .label_details
14855                    .as_ref()
14856                    .and_then(|label_details| label_details.description.as_ref())
14857                {
14858                    format!("{} {}", item.label, description)
14859                } else if let Some(detail) = &item.detail {
14860                    format!("{} {}", item.label, detail)
14861                } else {
14862                    item.label.clone()
14863                };
14864                let len = text.len();
14865                Some(language::CodeLabel {
14866                    text,
14867                    runs: Vec::new(),
14868                    filter_range: 0..len,
14869                })
14870            })),
14871            ..FakeLspAdapter::default()
14872        },
14873    );
14874    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14875    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14876    let worktree_id = workspace
14877        .update(cx, |workspace, _window, cx| {
14878            workspace.project().update(cx, |project, cx| {
14879                project.worktrees(cx).next().unwrap().read(cx).id()
14880            })
14881        })
14882        .unwrap();
14883    let _buffer = project
14884        .update(cx, |project, cx| {
14885            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14886        })
14887        .await
14888        .unwrap();
14889    let editor = workspace
14890        .update(cx, |workspace, window, cx| {
14891            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14892        })
14893        .unwrap()
14894        .await
14895        .unwrap()
14896        .downcast::<Editor>()
14897        .unwrap();
14898    let fake_server = fake_servers.next().await.unwrap();
14899
14900    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14901    let multiline_label_2 = "a\nb\nc\n";
14902    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14903    let multiline_description = "d\ne\nf\n";
14904    let multiline_detail_2 = "g\nh\ni\n";
14905
14906    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14907        move |params, _| async move {
14908            Ok(Some(lsp::CompletionResponse::Array(vec![
14909                lsp::CompletionItem {
14910                    label: multiline_label.to_string(),
14911                    text_edit: gen_text_edit(&params, "new_text_1"),
14912                    ..lsp::CompletionItem::default()
14913                },
14914                lsp::CompletionItem {
14915                    label: "single line label 1".to_string(),
14916                    detail: Some(multiline_detail.to_string()),
14917                    text_edit: gen_text_edit(&params, "new_text_2"),
14918                    ..lsp::CompletionItem::default()
14919                },
14920                lsp::CompletionItem {
14921                    label: "single line label 2".to_string(),
14922                    label_details: Some(lsp::CompletionItemLabelDetails {
14923                        description: Some(multiline_description.to_string()),
14924                        detail: None,
14925                    }),
14926                    text_edit: gen_text_edit(&params, "new_text_2"),
14927                    ..lsp::CompletionItem::default()
14928                },
14929                lsp::CompletionItem {
14930                    label: multiline_label_2.to_string(),
14931                    detail: Some(multiline_detail_2.to_string()),
14932                    text_edit: gen_text_edit(&params, "new_text_3"),
14933                    ..lsp::CompletionItem::default()
14934                },
14935                lsp::CompletionItem {
14936                    label: "Label with many     spaces and \t but without newlines".to_string(),
14937                    detail: Some(
14938                        "Details with many     spaces and \t but without newlines".to_string(),
14939                    ),
14940                    text_edit: gen_text_edit(&params, "new_text_4"),
14941                    ..lsp::CompletionItem::default()
14942                },
14943            ])))
14944        },
14945    );
14946
14947    editor.update_in(cx, |editor, window, cx| {
14948        cx.focus_self(window);
14949        editor.move_to_end(&MoveToEnd, window, cx);
14950        editor.handle_input(".", window, cx);
14951    });
14952    cx.run_until_parked();
14953    completion_handle.next().await.unwrap();
14954
14955    editor.update(cx, |editor, _| {
14956        assert!(editor.context_menu_visible());
14957        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14958        {
14959            let completion_labels = menu
14960                .completions
14961                .borrow()
14962                .iter()
14963                .map(|c| c.label.text.clone())
14964                .collect::<Vec<_>>();
14965            assert_eq!(
14966                completion_labels,
14967                &[
14968                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14969                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14970                    "single line label 2 d e f ",
14971                    "a b c g h i ",
14972                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14973                ],
14974                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14975            );
14976
14977            for completion in menu
14978                .completions
14979                .borrow()
14980                .iter() {
14981                    assert_eq!(
14982                        completion.label.filter_range,
14983                        0..completion.label.text.len(),
14984                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14985                    );
14986                }
14987        } else {
14988            panic!("expected completion menu to be open");
14989        }
14990    });
14991}
14992
14993#[gpui::test]
14994async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14995    init_test(cx, |_| {});
14996    let mut cx = EditorLspTestContext::new_rust(
14997        lsp::ServerCapabilities {
14998            completion_provider: Some(lsp::CompletionOptions {
14999                trigger_characters: Some(vec![".".to_string()]),
15000                ..Default::default()
15001            }),
15002            ..Default::default()
15003        },
15004        cx,
15005    )
15006    .await;
15007    cx.lsp
15008        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15009            Ok(Some(lsp::CompletionResponse::Array(vec![
15010                lsp::CompletionItem {
15011                    label: "first".into(),
15012                    ..Default::default()
15013                },
15014                lsp::CompletionItem {
15015                    label: "last".into(),
15016                    ..Default::default()
15017                },
15018            ])))
15019        });
15020    cx.set_state("variableˇ");
15021    cx.simulate_keystroke(".");
15022    cx.executor().run_until_parked();
15023
15024    cx.update_editor(|editor, _, _| {
15025        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15026        {
15027            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15028        } else {
15029            panic!("expected completion menu to be open");
15030        }
15031    });
15032
15033    cx.update_editor(|editor, window, cx| {
15034        editor.move_page_down(&MovePageDown::default(), window, cx);
15035        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15036        {
15037            assert!(
15038                menu.selected_item == 1,
15039                "expected PageDown to select the last item from the context menu"
15040            );
15041        } else {
15042            panic!("expected completion menu to stay open after PageDown");
15043        }
15044    });
15045
15046    cx.update_editor(|editor, window, cx| {
15047        editor.move_page_up(&MovePageUp::default(), window, cx);
15048        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15049        {
15050            assert!(
15051                menu.selected_item == 0,
15052                "expected PageUp to select the first item from the context menu"
15053            );
15054        } else {
15055            panic!("expected completion menu to stay open after PageUp");
15056        }
15057    });
15058}
15059
15060#[gpui::test]
15061async fn test_as_is_completions(cx: &mut TestAppContext) {
15062    init_test(cx, |_| {});
15063    let mut cx = EditorLspTestContext::new_rust(
15064        lsp::ServerCapabilities {
15065            completion_provider: Some(lsp::CompletionOptions {
15066                ..Default::default()
15067            }),
15068            ..Default::default()
15069        },
15070        cx,
15071    )
15072    .await;
15073    cx.lsp
15074        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15075            Ok(Some(lsp::CompletionResponse::Array(vec![
15076                lsp::CompletionItem {
15077                    label: "unsafe".into(),
15078                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15079                        range: lsp::Range {
15080                            start: lsp::Position {
15081                                line: 1,
15082                                character: 2,
15083                            },
15084                            end: lsp::Position {
15085                                line: 1,
15086                                character: 3,
15087                            },
15088                        },
15089                        new_text: "unsafe".to_string(),
15090                    })),
15091                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15092                    ..Default::default()
15093                },
15094            ])))
15095        });
15096    cx.set_state("fn a() {}\n");
15097    cx.executor().run_until_parked();
15098    cx.update_editor(|editor, window, cx| {
15099        editor.show_completions(
15100            &ShowCompletions {
15101                trigger: Some("\n".into()),
15102            },
15103            window,
15104            cx,
15105        );
15106    });
15107    cx.executor().run_until_parked();
15108
15109    cx.update_editor(|editor, window, cx| {
15110        editor.confirm_completion(&Default::default(), window, cx)
15111    });
15112    cx.executor().run_until_parked();
15113    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15114}
15115
15116#[gpui::test]
15117async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15118    init_test(cx, |_| {});
15119    let language =
15120        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15121    let mut cx = EditorLspTestContext::new(
15122        language,
15123        lsp::ServerCapabilities {
15124            completion_provider: Some(lsp::CompletionOptions {
15125                ..lsp::CompletionOptions::default()
15126            }),
15127            ..lsp::ServerCapabilities::default()
15128        },
15129        cx,
15130    )
15131    .await;
15132
15133    cx.set_state(
15134        "#ifndef BAR_H
15135#define BAR_H
15136
15137#include <stdbool.h>
15138
15139int fn_branch(bool do_branch1, bool do_branch2);
15140
15141#endif // BAR_H
15142ˇ",
15143    );
15144    cx.executor().run_until_parked();
15145    cx.update_editor(|editor, window, cx| {
15146        editor.handle_input("#", window, cx);
15147    });
15148    cx.executor().run_until_parked();
15149    cx.update_editor(|editor, window, cx| {
15150        editor.handle_input("i", window, cx);
15151    });
15152    cx.executor().run_until_parked();
15153    cx.update_editor(|editor, window, cx| {
15154        editor.handle_input("n", window, cx);
15155    });
15156    cx.executor().run_until_parked();
15157    cx.assert_editor_state(
15158        "#ifndef BAR_H
15159#define BAR_H
15160
15161#include <stdbool.h>
15162
15163int fn_branch(bool do_branch1, bool do_branch2);
15164
15165#endif // BAR_H
15166#inˇ",
15167    );
15168
15169    cx.lsp
15170        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15171            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15172                is_incomplete: false,
15173                item_defaults: None,
15174                items: vec![lsp::CompletionItem {
15175                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15176                    label_details: Some(lsp::CompletionItemLabelDetails {
15177                        detail: Some("header".to_string()),
15178                        description: None,
15179                    }),
15180                    label: " include".to_string(),
15181                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15182                        range: lsp::Range {
15183                            start: lsp::Position {
15184                                line: 8,
15185                                character: 1,
15186                            },
15187                            end: lsp::Position {
15188                                line: 8,
15189                                character: 1,
15190                            },
15191                        },
15192                        new_text: "include \"$0\"".to_string(),
15193                    })),
15194                    sort_text: Some("40b67681include".to_string()),
15195                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15196                    filter_text: Some("include".to_string()),
15197                    insert_text: Some("include \"$0\"".to_string()),
15198                    ..lsp::CompletionItem::default()
15199                }],
15200            })))
15201        });
15202    cx.update_editor(|editor, window, cx| {
15203        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15204    });
15205    cx.executor().run_until_parked();
15206    cx.update_editor(|editor, window, cx| {
15207        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15208    });
15209    cx.executor().run_until_parked();
15210    cx.assert_editor_state(
15211        "#ifndef BAR_H
15212#define BAR_H
15213
15214#include <stdbool.h>
15215
15216int fn_branch(bool do_branch1, bool do_branch2);
15217
15218#endif // BAR_H
15219#include \"ˇ\"",
15220    );
15221
15222    cx.lsp
15223        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15224            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15225                is_incomplete: true,
15226                item_defaults: None,
15227                items: vec![lsp::CompletionItem {
15228                    kind: Some(lsp::CompletionItemKind::FILE),
15229                    label: "AGL/".to_string(),
15230                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15231                        range: lsp::Range {
15232                            start: lsp::Position {
15233                                line: 8,
15234                                character: 10,
15235                            },
15236                            end: lsp::Position {
15237                                line: 8,
15238                                character: 11,
15239                            },
15240                        },
15241                        new_text: "AGL/".to_string(),
15242                    })),
15243                    sort_text: Some("40b67681AGL/".to_string()),
15244                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15245                    filter_text: Some("AGL/".to_string()),
15246                    insert_text: Some("AGL/".to_string()),
15247                    ..lsp::CompletionItem::default()
15248                }],
15249            })))
15250        });
15251    cx.update_editor(|editor, window, cx| {
15252        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15253    });
15254    cx.executor().run_until_parked();
15255    cx.update_editor(|editor, window, cx| {
15256        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15257    });
15258    cx.executor().run_until_parked();
15259    cx.assert_editor_state(
15260        r##"#ifndef BAR_H
15261#define BAR_H
15262
15263#include <stdbool.h>
15264
15265int fn_branch(bool do_branch1, bool do_branch2);
15266
15267#endif // BAR_H
15268#include "AGL/ˇ"##,
15269    );
15270
15271    cx.update_editor(|editor, window, cx| {
15272        editor.handle_input("\"", window, cx);
15273    });
15274    cx.executor().run_until_parked();
15275    cx.assert_editor_state(
15276        r##"#ifndef BAR_H
15277#define BAR_H
15278
15279#include <stdbool.h>
15280
15281int fn_branch(bool do_branch1, bool do_branch2);
15282
15283#endif // BAR_H
15284#include "AGL/"ˇ"##,
15285    );
15286}
15287
15288#[gpui::test]
15289async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15290    init_test(cx, |_| {});
15291
15292    let mut cx = EditorLspTestContext::new_rust(
15293        lsp::ServerCapabilities {
15294            completion_provider: Some(lsp::CompletionOptions {
15295                trigger_characters: Some(vec![".".to_string()]),
15296                resolve_provider: Some(true),
15297                ..Default::default()
15298            }),
15299            ..Default::default()
15300        },
15301        cx,
15302    )
15303    .await;
15304
15305    cx.set_state("fn main() { let a = 2ˇ; }");
15306    cx.simulate_keystroke(".");
15307    let completion_item = lsp::CompletionItem {
15308        label: "Some".into(),
15309        kind: Some(lsp::CompletionItemKind::SNIPPET),
15310        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15311        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15312            kind: lsp::MarkupKind::Markdown,
15313            value: "```rust\nSome(2)\n```".to_string(),
15314        })),
15315        deprecated: Some(false),
15316        sort_text: Some("Some".to_string()),
15317        filter_text: Some("Some".to_string()),
15318        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15319        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15320            range: lsp::Range {
15321                start: lsp::Position {
15322                    line: 0,
15323                    character: 22,
15324                },
15325                end: lsp::Position {
15326                    line: 0,
15327                    character: 22,
15328                },
15329            },
15330            new_text: "Some(2)".to_string(),
15331        })),
15332        additional_text_edits: Some(vec![lsp::TextEdit {
15333            range: lsp::Range {
15334                start: lsp::Position {
15335                    line: 0,
15336                    character: 20,
15337                },
15338                end: lsp::Position {
15339                    line: 0,
15340                    character: 22,
15341                },
15342            },
15343            new_text: "".to_string(),
15344        }]),
15345        ..Default::default()
15346    };
15347
15348    let closure_completion_item = completion_item.clone();
15349    let counter = Arc::new(AtomicUsize::new(0));
15350    let counter_clone = counter.clone();
15351    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15352        let task_completion_item = closure_completion_item.clone();
15353        counter_clone.fetch_add(1, atomic::Ordering::Release);
15354        async move {
15355            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15356                is_incomplete: true,
15357                item_defaults: None,
15358                items: vec![task_completion_item],
15359            })))
15360        }
15361    });
15362
15363    cx.condition(|editor, _| editor.context_menu_visible())
15364        .await;
15365    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15366    assert!(request.next().await.is_some());
15367    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15368
15369    cx.simulate_keystrokes("S o m");
15370    cx.condition(|editor, _| editor.context_menu_visible())
15371        .await;
15372    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15373    assert!(request.next().await.is_some());
15374    assert!(request.next().await.is_some());
15375    assert!(request.next().await.is_some());
15376    request.close();
15377    assert!(request.next().await.is_none());
15378    assert_eq!(
15379        counter.load(atomic::Ordering::Acquire),
15380        4,
15381        "With the completions menu open, only one LSP request should happen per input"
15382    );
15383}
15384
15385#[gpui::test]
15386async fn test_toggle_comment(cx: &mut TestAppContext) {
15387    init_test(cx, |_| {});
15388    let mut cx = EditorTestContext::new(cx).await;
15389    let language = Arc::new(Language::new(
15390        LanguageConfig {
15391            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15392            ..Default::default()
15393        },
15394        Some(tree_sitter_rust::LANGUAGE.into()),
15395    ));
15396    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15397
15398    // If multiple selections intersect a line, the line is only toggled once.
15399    cx.set_state(indoc! {"
15400        fn a() {
15401            «//b();
15402            ˇ»// «c();
15403            //ˇ»  d();
15404        }
15405    "});
15406
15407    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15408
15409    cx.assert_editor_state(indoc! {"
15410        fn a() {
15411            «b();
15412            c();
15413            ˇ» d();
15414        }
15415    "});
15416
15417    // The comment prefix is inserted at the same column for every line in a
15418    // selection.
15419    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15420
15421    cx.assert_editor_state(indoc! {"
15422        fn a() {
15423            // «b();
15424            // c();
15425            ˇ»//  d();
15426        }
15427    "});
15428
15429    // If a selection ends at the beginning of a line, that line is not toggled.
15430    cx.set_selections_state(indoc! {"
15431        fn a() {
15432            // b();
15433            «// c();
15434        ˇ»    //  d();
15435        }
15436    "});
15437
15438    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15439
15440    cx.assert_editor_state(indoc! {"
15441        fn a() {
15442            // b();
15443            «c();
15444        ˇ»    //  d();
15445        }
15446    "});
15447
15448    // If a selection span a single line and is empty, the line is toggled.
15449    cx.set_state(indoc! {"
15450        fn a() {
15451            a();
15452            b();
15453        ˇ
15454        }
15455    "});
15456
15457    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15458
15459    cx.assert_editor_state(indoc! {"
15460        fn a() {
15461            a();
15462            b();
15463        //•ˇ
15464        }
15465    "});
15466
15467    // If a selection span multiple lines, empty lines are not toggled.
15468    cx.set_state(indoc! {"
15469        fn a() {
15470            «a();
15471
15472            c();ˇ»
15473        }
15474    "});
15475
15476    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15477
15478    cx.assert_editor_state(indoc! {"
15479        fn a() {
15480            // «a();
15481
15482            // c();ˇ»
15483        }
15484    "});
15485
15486    // If a selection includes multiple comment prefixes, all lines are uncommented.
15487    cx.set_state(indoc! {"
15488        fn a() {
15489            «// a();
15490            /// b();
15491            //! c();ˇ»
15492        }
15493    "});
15494
15495    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15496
15497    cx.assert_editor_state(indoc! {"
15498        fn a() {
15499            «a();
15500            b();
15501            c();ˇ»
15502        }
15503    "});
15504}
15505
15506#[gpui::test]
15507async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15508    init_test(cx, |_| {});
15509    let mut cx = EditorTestContext::new(cx).await;
15510    let language = Arc::new(Language::new(
15511        LanguageConfig {
15512            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15513            ..Default::default()
15514        },
15515        Some(tree_sitter_rust::LANGUAGE.into()),
15516    ));
15517    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15518
15519    let toggle_comments = &ToggleComments {
15520        advance_downwards: false,
15521        ignore_indent: true,
15522    };
15523
15524    // If multiple selections intersect a line, the line is only toggled once.
15525    cx.set_state(indoc! {"
15526        fn a() {
15527        //    «b();
15528        //    c();
15529        //    ˇ» d();
15530        }
15531    "});
15532
15533    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15534
15535    cx.assert_editor_state(indoc! {"
15536        fn a() {
15537            «b();
15538            c();
15539            ˇ» d();
15540        }
15541    "});
15542
15543    // The comment prefix is inserted at the beginning of each line
15544    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15545
15546    cx.assert_editor_state(indoc! {"
15547        fn a() {
15548        //    «b();
15549        //    c();
15550        //    ˇ» d();
15551        }
15552    "});
15553
15554    // If a selection ends at the beginning of a line, that line is not toggled.
15555    cx.set_selections_state(indoc! {"
15556        fn a() {
15557        //    b();
15558        //    «c();
15559        ˇ»//     d();
15560        }
15561    "});
15562
15563    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15564
15565    cx.assert_editor_state(indoc! {"
15566        fn a() {
15567        //    b();
15568            «c();
15569        ˇ»//     d();
15570        }
15571    "});
15572
15573    // If a selection span a single line and is empty, the line is toggled.
15574    cx.set_state(indoc! {"
15575        fn a() {
15576            a();
15577            b();
15578        ˇ
15579        }
15580    "});
15581
15582    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15583
15584    cx.assert_editor_state(indoc! {"
15585        fn a() {
15586            a();
15587            b();
15588        //ˇ
15589        }
15590    "});
15591
15592    // If a selection span multiple lines, empty lines are not toggled.
15593    cx.set_state(indoc! {"
15594        fn a() {
15595            «a();
15596
15597            c();ˇ»
15598        }
15599    "});
15600
15601    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15602
15603    cx.assert_editor_state(indoc! {"
15604        fn a() {
15605        //    «a();
15606
15607        //    c();ˇ»
15608        }
15609    "});
15610
15611    // If a selection includes multiple comment prefixes, all lines are uncommented.
15612    cx.set_state(indoc! {"
15613        fn a() {
15614        //    «a();
15615        ///    b();
15616        //!    c();ˇ»
15617        }
15618    "});
15619
15620    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15621
15622    cx.assert_editor_state(indoc! {"
15623        fn a() {
15624            «a();
15625            b();
15626            c();ˇ»
15627        }
15628    "});
15629}
15630
15631#[gpui::test]
15632async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15633    init_test(cx, |_| {});
15634
15635    let language = Arc::new(Language::new(
15636        LanguageConfig {
15637            line_comments: vec!["// ".into()],
15638            ..Default::default()
15639        },
15640        Some(tree_sitter_rust::LANGUAGE.into()),
15641    ));
15642
15643    let mut cx = EditorTestContext::new(cx).await;
15644
15645    cx.language_registry().add(language.clone());
15646    cx.update_buffer(|buffer, cx| {
15647        buffer.set_language(Some(language), cx);
15648    });
15649
15650    let toggle_comments = &ToggleComments {
15651        advance_downwards: true,
15652        ignore_indent: false,
15653    };
15654
15655    // Single cursor on one line -> advance
15656    // Cursor moves horizontally 3 characters as well on non-blank line
15657    cx.set_state(indoc!(
15658        "fn a() {
15659             ˇdog();
15660             cat();
15661        }"
15662    ));
15663    cx.update_editor(|editor, window, cx| {
15664        editor.toggle_comments(toggle_comments, window, cx);
15665    });
15666    cx.assert_editor_state(indoc!(
15667        "fn a() {
15668             // dog();
15669             catˇ();
15670        }"
15671    ));
15672
15673    // Single selection on one line -> don't advance
15674    cx.set_state(indoc!(
15675        "fn a() {
15676             «dog()ˇ»;
15677             cat();
15678        }"
15679    ));
15680    cx.update_editor(|editor, window, cx| {
15681        editor.toggle_comments(toggle_comments, window, cx);
15682    });
15683    cx.assert_editor_state(indoc!(
15684        "fn a() {
15685             // «dog()ˇ»;
15686             cat();
15687        }"
15688    ));
15689
15690    // Multiple cursors on one line -> advance
15691    cx.set_state(indoc!(
15692        "fn a() {
15693             ˇdˇog();
15694             cat();
15695        }"
15696    ));
15697    cx.update_editor(|editor, window, cx| {
15698        editor.toggle_comments(toggle_comments, window, cx);
15699    });
15700    cx.assert_editor_state(indoc!(
15701        "fn a() {
15702             // dog();
15703             catˇ(ˇ);
15704        }"
15705    ));
15706
15707    // Multiple cursors on one line, with selection -> don't advance
15708    cx.set_state(indoc!(
15709        "fn a() {
15710             ˇdˇog«()ˇ»;
15711             cat();
15712        }"
15713    ));
15714    cx.update_editor(|editor, window, cx| {
15715        editor.toggle_comments(toggle_comments, window, cx);
15716    });
15717    cx.assert_editor_state(indoc!(
15718        "fn a() {
15719             // ˇdˇog«()ˇ»;
15720             cat();
15721        }"
15722    ));
15723
15724    // Single cursor on one line -> advance
15725    // Cursor moves to column 0 on blank line
15726    cx.set_state(indoc!(
15727        "fn a() {
15728             ˇdog();
15729
15730             cat();
15731        }"
15732    ));
15733    cx.update_editor(|editor, window, cx| {
15734        editor.toggle_comments(toggle_comments, window, cx);
15735    });
15736    cx.assert_editor_state(indoc!(
15737        "fn a() {
15738             // dog();
15739        ˇ
15740             cat();
15741        }"
15742    ));
15743
15744    // Single cursor on one line -> advance
15745    // Cursor starts and ends at column 0
15746    cx.set_state(indoc!(
15747        "fn a() {
15748         ˇ    dog();
15749             cat();
15750        }"
15751    ));
15752    cx.update_editor(|editor, window, cx| {
15753        editor.toggle_comments(toggle_comments, window, cx);
15754    });
15755    cx.assert_editor_state(indoc!(
15756        "fn a() {
15757             // dog();
15758         ˇ    cat();
15759        }"
15760    ));
15761}
15762
15763#[gpui::test]
15764async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15765    init_test(cx, |_| {});
15766
15767    let mut cx = EditorTestContext::new(cx).await;
15768
15769    let html_language = Arc::new(
15770        Language::new(
15771            LanguageConfig {
15772                name: "HTML".into(),
15773                block_comment: Some(BlockCommentConfig {
15774                    start: "<!-- ".into(),
15775                    prefix: "".into(),
15776                    end: " -->".into(),
15777                    tab_size: 0,
15778                }),
15779                ..Default::default()
15780            },
15781            Some(tree_sitter_html::LANGUAGE.into()),
15782        )
15783        .with_injection_query(
15784            r#"
15785            (script_element
15786                (raw_text) @injection.content
15787                (#set! injection.language "javascript"))
15788            "#,
15789        )
15790        .unwrap(),
15791    );
15792
15793    let javascript_language = Arc::new(Language::new(
15794        LanguageConfig {
15795            name: "JavaScript".into(),
15796            line_comments: vec!["// ".into()],
15797            ..Default::default()
15798        },
15799        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15800    ));
15801
15802    cx.language_registry().add(html_language.clone());
15803    cx.language_registry().add(javascript_language);
15804    cx.update_buffer(|buffer, cx| {
15805        buffer.set_language(Some(html_language), cx);
15806    });
15807
15808    // Toggle comments for empty selections
15809    cx.set_state(
15810        &r#"
15811            <p>A</p>ˇ
15812            <p>B</p>ˇ
15813            <p>C</p>ˇ
15814        "#
15815        .unindent(),
15816    );
15817    cx.update_editor(|editor, window, cx| {
15818        editor.toggle_comments(&ToggleComments::default(), window, cx)
15819    });
15820    cx.assert_editor_state(
15821        &r#"
15822            <!-- <p>A</p>ˇ -->
15823            <!-- <p>B</p>ˇ -->
15824            <!-- <p>C</p>ˇ -->
15825        "#
15826        .unindent(),
15827    );
15828    cx.update_editor(|editor, window, cx| {
15829        editor.toggle_comments(&ToggleComments::default(), window, cx)
15830    });
15831    cx.assert_editor_state(
15832        &r#"
15833            <p>A</p>ˇ
15834            <p>B</p>ˇ
15835            <p>C</p>ˇ
15836        "#
15837        .unindent(),
15838    );
15839
15840    // Toggle comments for mixture of empty and non-empty selections, where
15841    // multiple selections occupy a given line.
15842    cx.set_state(
15843        &r#"
15844            <p>A«</p>
15845            <p>ˇ»B</p>ˇ
15846            <p>C«</p>
15847            <p>ˇ»D</p>ˇ
15848        "#
15849        .unindent(),
15850    );
15851
15852    cx.update_editor(|editor, window, cx| {
15853        editor.toggle_comments(&ToggleComments::default(), window, cx)
15854    });
15855    cx.assert_editor_state(
15856        &r#"
15857            <!-- <p>A«</p>
15858            <p>ˇ»B</p>ˇ -->
15859            <!-- <p>C«</p>
15860            <p>ˇ»D</p>ˇ -->
15861        "#
15862        .unindent(),
15863    );
15864    cx.update_editor(|editor, window, cx| {
15865        editor.toggle_comments(&ToggleComments::default(), window, cx)
15866    });
15867    cx.assert_editor_state(
15868        &r#"
15869            <p>A«</p>
15870            <p>ˇ»B</p>ˇ
15871            <p>C«</p>
15872            <p>ˇ»D</p>ˇ
15873        "#
15874        .unindent(),
15875    );
15876
15877    // Toggle comments when different languages are active for different
15878    // selections.
15879    cx.set_state(
15880        &r#"
15881            ˇ<script>
15882                ˇvar x = new Y();
15883            ˇ</script>
15884        "#
15885        .unindent(),
15886    );
15887    cx.executor().run_until_parked();
15888    cx.update_editor(|editor, window, cx| {
15889        editor.toggle_comments(&ToggleComments::default(), window, cx)
15890    });
15891    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15892    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15893    cx.assert_editor_state(
15894        &r#"
15895            <!-- ˇ<script> -->
15896                // ˇvar x = new Y();
15897            <!-- ˇ</script> -->
15898        "#
15899        .unindent(),
15900    );
15901}
15902
15903#[gpui::test]
15904fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15905    init_test(cx, |_| {});
15906
15907    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15908    let multibuffer = cx.new(|cx| {
15909        let mut multibuffer = MultiBuffer::new(ReadWrite);
15910        multibuffer.push_excerpts(
15911            buffer.clone(),
15912            [
15913                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15914                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15915            ],
15916            cx,
15917        );
15918        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15919        multibuffer
15920    });
15921
15922    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15923    editor.update_in(cx, |editor, window, cx| {
15924        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15926            s.select_ranges([
15927                Point::new(0, 0)..Point::new(0, 0),
15928                Point::new(1, 0)..Point::new(1, 0),
15929            ])
15930        });
15931
15932        editor.handle_input("X", window, cx);
15933        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15934        assert_eq!(
15935            editor.selections.ranges(cx),
15936            [
15937                Point::new(0, 1)..Point::new(0, 1),
15938                Point::new(1, 1)..Point::new(1, 1),
15939            ]
15940        );
15941
15942        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15944            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15945        });
15946        editor.backspace(&Default::default(), window, cx);
15947        assert_eq!(editor.text(cx), "Xa\nbbb");
15948        assert_eq!(
15949            editor.selections.ranges(cx),
15950            [Point::new(1, 0)..Point::new(1, 0)]
15951        );
15952
15953        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15954            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15955        });
15956        editor.backspace(&Default::default(), window, cx);
15957        assert_eq!(editor.text(cx), "X\nbb");
15958        assert_eq!(
15959            editor.selections.ranges(cx),
15960            [Point::new(0, 1)..Point::new(0, 1)]
15961        );
15962    });
15963}
15964
15965#[gpui::test]
15966fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15967    init_test(cx, |_| {});
15968
15969    let markers = vec![('[', ']').into(), ('(', ')').into()];
15970    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15971        indoc! {"
15972            [aaaa
15973            (bbbb]
15974            cccc)",
15975        },
15976        markers.clone(),
15977    );
15978    let excerpt_ranges = markers.into_iter().map(|marker| {
15979        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15980        ExcerptRange::new(context)
15981    });
15982    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15983    let multibuffer = cx.new(|cx| {
15984        let mut multibuffer = MultiBuffer::new(ReadWrite);
15985        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15986        multibuffer
15987    });
15988
15989    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15990    editor.update_in(cx, |editor, window, cx| {
15991        let (expected_text, selection_ranges) = marked_text_ranges(
15992            indoc! {"
15993                aaaa
15994                bˇbbb
15995                bˇbbˇb
15996                cccc"
15997            },
15998            true,
15999        );
16000        assert_eq!(editor.text(cx), expected_text);
16001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16002            s.select_ranges(selection_ranges)
16003        });
16004
16005        editor.handle_input("X", window, cx);
16006
16007        let (expected_text, expected_selections) = marked_text_ranges(
16008            indoc! {"
16009                aaaa
16010                bXˇbbXb
16011                bXˇbbXˇb
16012                cccc"
16013            },
16014            false,
16015        );
16016        assert_eq!(editor.text(cx), expected_text);
16017        assert_eq!(editor.selections.ranges(cx), expected_selections);
16018
16019        editor.newline(&Newline, window, cx);
16020        let (expected_text, expected_selections) = marked_text_ranges(
16021            indoc! {"
16022                aaaa
16023                bX
16024                ˇbbX
16025                b
16026                bX
16027                ˇbbX
16028                ˇb
16029                cccc"
16030            },
16031            false,
16032        );
16033        assert_eq!(editor.text(cx), expected_text);
16034        assert_eq!(editor.selections.ranges(cx), expected_selections);
16035    });
16036}
16037
16038#[gpui::test]
16039fn test_refresh_selections(cx: &mut TestAppContext) {
16040    init_test(cx, |_| {});
16041
16042    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16043    let mut excerpt1_id = None;
16044    let multibuffer = cx.new(|cx| {
16045        let mut multibuffer = MultiBuffer::new(ReadWrite);
16046        excerpt1_id = multibuffer
16047            .push_excerpts(
16048                buffer.clone(),
16049                [
16050                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16051                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16052                ],
16053                cx,
16054            )
16055            .into_iter()
16056            .next();
16057        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16058        multibuffer
16059    });
16060
16061    let editor = cx.add_window(|window, cx| {
16062        let mut editor = build_editor(multibuffer.clone(), window, cx);
16063        let snapshot = editor.snapshot(window, cx);
16064        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16065            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16066        });
16067        editor.begin_selection(
16068            Point::new(2, 1).to_display_point(&snapshot),
16069            true,
16070            1,
16071            window,
16072            cx,
16073        );
16074        assert_eq!(
16075            editor.selections.ranges(cx),
16076            [
16077                Point::new(1, 3)..Point::new(1, 3),
16078                Point::new(2, 1)..Point::new(2, 1),
16079            ]
16080        );
16081        editor
16082    });
16083
16084    // Refreshing selections is a no-op when excerpts haven't changed.
16085    _ = editor.update(cx, |editor, window, cx| {
16086        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16087        assert_eq!(
16088            editor.selections.ranges(cx),
16089            [
16090                Point::new(1, 3)..Point::new(1, 3),
16091                Point::new(2, 1)..Point::new(2, 1),
16092            ]
16093        );
16094    });
16095
16096    multibuffer.update(cx, |multibuffer, cx| {
16097        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16098    });
16099    _ = editor.update(cx, |editor, window, cx| {
16100        // Removing an excerpt causes the first selection to become degenerate.
16101        assert_eq!(
16102            editor.selections.ranges(cx),
16103            [
16104                Point::new(0, 0)..Point::new(0, 0),
16105                Point::new(0, 1)..Point::new(0, 1)
16106            ]
16107        );
16108
16109        // Refreshing selections will relocate the first selection to the original buffer
16110        // location.
16111        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16112        assert_eq!(
16113            editor.selections.ranges(cx),
16114            [
16115                Point::new(0, 1)..Point::new(0, 1),
16116                Point::new(0, 3)..Point::new(0, 3)
16117            ]
16118        );
16119        assert!(editor.selections.pending_anchor().is_some());
16120    });
16121}
16122
16123#[gpui::test]
16124fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16125    init_test(cx, |_| {});
16126
16127    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16128    let mut excerpt1_id = None;
16129    let multibuffer = cx.new(|cx| {
16130        let mut multibuffer = MultiBuffer::new(ReadWrite);
16131        excerpt1_id = multibuffer
16132            .push_excerpts(
16133                buffer.clone(),
16134                [
16135                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16136                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16137                ],
16138                cx,
16139            )
16140            .into_iter()
16141            .next();
16142        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16143        multibuffer
16144    });
16145
16146    let editor = cx.add_window(|window, cx| {
16147        let mut editor = build_editor(multibuffer.clone(), window, cx);
16148        let snapshot = editor.snapshot(window, cx);
16149        editor.begin_selection(
16150            Point::new(1, 3).to_display_point(&snapshot),
16151            false,
16152            1,
16153            window,
16154            cx,
16155        );
16156        assert_eq!(
16157            editor.selections.ranges(cx),
16158            [Point::new(1, 3)..Point::new(1, 3)]
16159        );
16160        editor
16161    });
16162
16163    multibuffer.update(cx, |multibuffer, cx| {
16164        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16165    });
16166    _ = editor.update(cx, |editor, window, cx| {
16167        assert_eq!(
16168            editor.selections.ranges(cx),
16169            [Point::new(0, 0)..Point::new(0, 0)]
16170        );
16171
16172        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16174        assert_eq!(
16175            editor.selections.ranges(cx),
16176            [Point::new(0, 3)..Point::new(0, 3)]
16177        );
16178        assert!(editor.selections.pending_anchor().is_some());
16179    });
16180}
16181
16182#[gpui::test]
16183async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16184    init_test(cx, |_| {});
16185
16186    let language = Arc::new(
16187        Language::new(
16188            LanguageConfig {
16189                brackets: BracketPairConfig {
16190                    pairs: vec![
16191                        BracketPair {
16192                            start: "{".to_string(),
16193                            end: "}".to_string(),
16194                            close: true,
16195                            surround: true,
16196                            newline: true,
16197                        },
16198                        BracketPair {
16199                            start: "/* ".to_string(),
16200                            end: " */".to_string(),
16201                            close: true,
16202                            surround: true,
16203                            newline: true,
16204                        },
16205                    ],
16206                    ..Default::default()
16207                },
16208                ..Default::default()
16209            },
16210            Some(tree_sitter_rust::LANGUAGE.into()),
16211        )
16212        .with_indents_query("")
16213        .unwrap(),
16214    );
16215
16216    let text = concat!(
16217        "{   }\n",     //
16218        "  x\n",       //
16219        "  /*   */\n", //
16220        "x\n",         //
16221        "{{} }\n",     //
16222    );
16223
16224    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16225    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16226    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16227    editor
16228        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16229        .await;
16230
16231    editor.update_in(cx, |editor, window, cx| {
16232        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16233            s.select_display_ranges([
16234                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16235                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16236                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16237            ])
16238        });
16239        editor.newline(&Newline, window, cx);
16240
16241        assert_eq!(
16242            editor.buffer().read(cx).read(cx).text(),
16243            concat!(
16244                "{ \n",    // Suppress rustfmt
16245                "\n",      //
16246                "}\n",     //
16247                "  x\n",   //
16248                "  /* \n", //
16249                "  \n",    //
16250                "  */\n",  //
16251                "x\n",     //
16252                "{{} \n",  //
16253                "}\n",     //
16254            )
16255        );
16256    });
16257}
16258
16259#[gpui::test]
16260fn test_highlighted_ranges(cx: &mut TestAppContext) {
16261    init_test(cx, |_| {});
16262
16263    let editor = cx.add_window(|window, cx| {
16264        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16265        build_editor(buffer, window, cx)
16266    });
16267
16268    _ = editor.update(cx, |editor, window, cx| {
16269        struct Type1;
16270        struct Type2;
16271
16272        let buffer = editor.buffer.read(cx).snapshot(cx);
16273
16274        let anchor_range =
16275            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16276
16277        editor.highlight_background::<Type1>(
16278            &[
16279                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16280                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16281                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16282                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16283            ],
16284            |_| Hsla::red(),
16285            cx,
16286        );
16287        editor.highlight_background::<Type2>(
16288            &[
16289                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16290                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16291                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16292                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16293            ],
16294            |_| Hsla::green(),
16295            cx,
16296        );
16297
16298        let snapshot = editor.snapshot(window, cx);
16299        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16300            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16301            &snapshot,
16302            cx.theme(),
16303        );
16304        assert_eq!(
16305            highlighted_ranges,
16306            &[
16307                (
16308                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16309                    Hsla::green(),
16310                ),
16311                (
16312                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16313                    Hsla::red(),
16314                ),
16315                (
16316                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16317                    Hsla::green(),
16318                ),
16319                (
16320                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16321                    Hsla::red(),
16322                ),
16323            ]
16324        );
16325        assert_eq!(
16326            editor.sorted_background_highlights_in_range(
16327                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16328                &snapshot,
16329                cx.theme(),
16330            ),
16331            &[(
16332                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16333                Hsla::red(),
16334            )]
16335        );
16336    });
16337}
16338
16339#[gpui::test]
16340async fn test_following(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
16346    let buffer = project.update(cx, |project, cx| {
16347        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16348        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16349    });
16350    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16351    let follower = cx.update(|cx| {
16352        cx.open_window(
16353            WindowOptions {
16354                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16355                    gpui::Point::new(px(0.), px(0.)),
16356                    gpui::Point::new(px(10.), px(80.)),
16357                ))),
16358                ..Default::default()
16359            },
16360            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16361        )
16362        .unwrap()
16363    });
16364
16365    let is_still_following = Rc::new(RefCell::new(true));
16366    let follower_edit_event_count = Rc::new(RefCell::new(0));
16367    let pending_update = Rc::new(RefCell::new(None));
16368    let leader_entity = leader.root(cx).unwrap();
16369    let follower_entity = follower.root(cx).unwrap();
16370    _ = follower.update(cx, {
16371        let update = pending_update.clone();
16372        let is_still_following = is_still_following.clone();
16373        let follower_edit_event_count = follower_edit_event_count.clone();
16374        |_, window, cx| {
16375            cx.subscribe_in(
16376                &leader_entity,
16377                window,
16378                move |_, leader, event, window, cx| {
16379                    leader.read(cx).add_event_to_update_proto(
16380                        event,
16381                        &mut update.borrow_mut(),
16382                        window,
16383                        cx,
16384                    );
16385                },
16386            )
16387            .detach();
16388
16389            cx.subscribe_in(
16390                &follower_entity,
16391                window,
16392                move |_, _, event: &EditorEvent, _window, _cx| {
16393                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16394                        *is_still_following.borrow_mut() = false;
16395                    }
16396
16397                    if let EditorEvent::BufferEdited = event {
16398                        *follower_edit_event_count.borrow_mut() += 1;
16399                    }
16400                },
16401            )
16402            .detach();
16403        }
16404    });
16405
16406    // Update the selections only
16407    _ = leader.update(cx, |leader, window, cx| {
16408        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16409            s.select_ranges([1..1])
16410        });
16411    });
16412    follower
16413        .update(cx, |follower, window, cx| {
16414            follower.apply_update_proto(
16415                &project,
16416                pending_update.borrow_mut().take().unwrap(),
16417                window,
16418                cx,
16419            )
16420        })
16421        .unwrap()
16422        .await
16423        .unwrap();
16424    _ = follower.update(cx, |follower, _, cx| {
16425        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16426    });
16427    assert!(*is_still_following.borrow());
16428    assert_eq!(*follower_edit_event_count.borrow(), 0);
16429
16430    // Update the scroll position only
16431    _ = leader.update(cx, |leader, window, cx| {
16432        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16433    });
16434    follower
16435        .update(cx, |follower, window, cx| {
16436            follower.apply_update_proto(
16437                &project,
16438                pending_update.borrow_mut().take().unwrap(),
16439                window,
16440                cx,
16441            )
16442        })
16443        .unwrap()
16444        .await
16445        .unwrap();
16446    assert_eq!(
16447        follower
16448            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16449            .unwrap(),
16450        gpui::Point::new(1.5, 3.5)
16451    );
16452    assert!(*is_still_following.borrow());
16453    assert_eq!(*follower_edit_event_count.borrow(), 0);
16454
16455    // Update the selections and scroll position. The follower's scroll position is updated
16456    // via autoscroll, not via the leader's exact scroll position.
16457    _ = leader.update(cx, |leader, window, cx| {
16458        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16459            s.select_ranges([0..0])
16460        });
16461        leader.request_autoscroll(Autoscroll::newest(), cx);
16462        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16463    });
16464    follower
16465        .update(cx, |follower, window, cx| {
16466            follower.apply_update_proto(
16467                &project,
16468                pending_update.borrow_mut().take().unwrap(),
16469                window,
16470                cx,
16471            )
16472        })
16473        .unwrap()
16474        .await
16475        .unwrap();
16476    _ = follower.update(cx, |follower, _, cx| {
16477        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16478        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16479    });
16480    assert!(*is_still_following.borrow());
16481
16482    // Creating a pending selection that precedes another selection
16483    _ = leader.update(cx, |leader, window, cx| {
16484        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16485            s.select_ranges([1..1])
16486        });
16487        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16488    });
16489    follower
16490        .update(cx, |follower, window, cx| {
16491            follower.apply_update_proto(
16492                &project,
16493                pending_update.borrow_mut().take().unwrap(),
16494                window,
16495                cx,
16496            )
16497        })
16498        .unwrap()
16499        .await
16500        .unwrap();
16501    _ = follower.update(cx, |follower, _, cx| {
16502        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16503    });
16504    assert!(*is_still_following.borrow());
16505
16506    // Extend the pending selection so that it surrounds another selection
16507    _ = leader.update(cx, |leader, window, cx| {
16508        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16509    });
16510    follower
16511        .update(cx, |follower, window, cx| {
16512            follower.apply_update_proto(
16513                &project,
16514                pending_update.borrow_mut().take().unwrap(),
16515                window,
16516                cx,
16517            )
16518        })
16519        .unwrap()
16520        .await
16521        .unwrap();
16522    _ = follower.update(cx, |follower, _, cx| {
16523        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16524    });
16525
16526    // Scrolling locally breaks the follow
16527    _ = follower.update(cx, |follower, window, cx| {
16528        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16529        follower.set_scroll_anchor(
16530            ScrollAnchor {
16531                anchor: top_anchor,
16532                offset: gpui::Point::new(0.0, 0.5),
16533            },
16534            window,
16535            cx,
16536        );
16537    });
16538    assert!(!(*is_still_following.borrow()));
16539}
16540
16541#[gpui::test]
16542async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16543    init_test(cx, |_| {});
16544
16545    let fs = FakeFs::new(cx.executor());
16546    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16547    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16548    let pane = workspace
16549        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16550        .unwrap();
16551
16552    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16553
16554    let leader = pane.update_in(cx, |_, window, cx| {
16555        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16556        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16557    });
16558
16559    // Start following the editor when it has no excerpts.
16560    let mut state_message =
16561        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16562    let workspace_entity = workspace.root(cx).unwrap();
16563    let follower_1 = cx
16564        .update_window(*workspace.deref(), |_, window, cx| {
16565            Editor::from_state_proto(
16566                workspace_entity,
16567                ViewId {
16568                    creator: CollaboratorId::PeerId(PeerId::default()),
16569                    id: 0,
16570                },
16571                &mut state_message,
16572                window,
16573                cx,
16574            )
16575        })
16576        .unwrap()
16577        .unwrap()
16578        .await
16579        .unwrap();
16580
16581    let update_message = Rc::new(RefCell::new(None));
16582    follower_1.update_in(cx, {
16583        let update = update_message.clone();
16584        |_, window, cx| {
16585            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16586                leader.read(cx).add_event_to_update_proto(
16587                    event,
16588                    &mut update.borrow_mut(),
16589                    window,
16590                    cx,
16591                );
16592            })
16593            .detach();
16594        }
16595    });
16596
16597    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16598        (
16599            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16600            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16601        )
16602    });
16603
16604    // Insert some excerpts.
16605    leader.update(cx, |leader, cx| {
16606        leader.buffer.update(cx, |multibuffer, cx| {
16607            multibuffer.set_excerpts_for_path(
16608                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16609                buffer_1.clone(),
16610                vec![
16611                    Point::row_range(0..3),
16612                    Point::row_range(1..6),
16613                    Point::row_range(12..15),
16614                ],
16615                0,
16616                cx,
16617            );
16618            multibuffer.set_excerpts_for_path(
16619                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16620                buffer_2.clone(),
16621                vec![Point::row_range(0..6), Point::row_range(8..12)],
16622                0,
16623                cx,
16624            );
16625        });
16626    });
16627
16628    // Apply the update of adding the excerpts.
16629    follower_1
16630        .update_in(cx, |follower, window, cx| {
16631            follower.apply_update_proto(
16632                &project,
16633                update_message.borrow().clone().unwrap(),
16634                window,
16635                cx,
16636            )
16637        })
16638        .await
16639        .unwrap();
16640    assert_eq!(
16641        follower_1.update(cx, |editor, cx| editor.text(cx)),
16642        leader.update(cx, |editor, cx| editor.text(cx))
16643    );
16644    update_message.borrow_mut().take();
16645
16646    // Start following separately after it already has excerpts.
16647    let mut state_message =
16648        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16649    let workspace_entity = workspace.root(cx).unwrap();
16650    let follower_2 = cx
16651        .update_window(*workspace.deref(), |_, window, cx| {
16652            Editor::from_state_proto(
16653                workspace_entity,
16654                ViewId {
16655                    creator: CollaboratorId::PeerId(PeerId::default()),
16656                    id: 0,
16657                },
16658                &mut state_message,
16659                window,
16660                cx,
16661            )
16662        })
16663        .unwrap()
16664        .unwrap()
16665        .await
16666        .unwrap();
16667    assert_eq!(
16668        follower_2.update(cx, |editor, cx| editor.text(cx)),
16669        leader.update(cx, |editor, cx| editor.text(cx))
16670    );
16671
16672    // Remove some excerpts.
16673    leader.update(cx, |leader, cx| {
16674        leader.buffer.update(cx, |multibuffer, cx| {
16675            let excerpt_ids = multibuffer.excerpt_ids();
16676            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16677            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16678        });
16679    });
16680
16681    // Apply the update of removing the excerpts.
16682    follower_1
16683        .update_in(cx, |follower, window, cx| {
16684            follower.apply_update_proto(
16685                &project,
16686                update_message.borrow().clone().unwrap(),
16687                window,
16688                cx,
16689            )
16690        })
16691        .await
16692        .unwrap();
16693    follower_2
16694        .update_in(cx, |follower, window, cx| {
16695            follower.apply_update_proto(
16696                &project,
16697                update_message.borrow().clone().unwrap(),
16698                window,
16699                cx,
16700            )
16701        })
16702        .await
16703        .unwrap();
16704    update_message.borrow_mut().take();
16705    assert_eq!(
16706        follower_1.update(cx, |editor, cx| editor.text(cx)),
16707        leader.update(cx, |editor, cx| editor.text(cx))
16708    );
16709}
16710
16711#[gpui::test]
16712async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16713    init_test(cx, |_| {});
16714
16715    let mut cx = EditorTestContext::new(cx).await;
16716    let lsp_store =
16717        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16718
16719    cx.set_state(indoc! {"
16720        ˇfn func(abc def: i32) -> u32 {
16721        }
16722    "});
16723
16724    cx.update(|_, cx| {
16725        lsp_store.update(cx, |lsp_store, cx| {
16726            lsp_store
16727                .update_diagnostics(
16728                    LanguageServerId(0),
16729                    lsp::PublishDiagnosticsParams {
16730                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16731                        version: None,
16732                        diagnostics: vec![
16733                            lsp::Diagnostic {
16734                                range: lsp::Range::new(
16735                                    lsp::Position::new(0, 11),
16736                                    lsp::Position::new(0, 12),
16737                                ),
16738                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16739                                ..Default::default()
16740                            },
16741                            lsp::Diagnostic {
16742                                range: lsp::Range::new(
16743                                    lsp::Position::new(0, 12),
16744                                    lsp::Position::new(0, 15),
16745                                ),
16746                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16747                                ..Default::default()
16748                            },
16749                            lsp::Diagnostic {
16750                                range: lsp::Range::new(
16751                                    lsp::Position::new(0, 25),
16752                                    lsp::Position::new(0, 28),
16753                                ),
16754                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16755                                ..Default::default()
16756                            },
16757                        ],
16758                    },
16759                    None,
16760                    DiagnosticSourceKind::Pushed,
16761                    &[],
16762                    cx,
16763                )
16764                .unwrap()
16765        });
16766    });
16767
16768    executor.run_until_parked();
16769
16770    cx.update_editor(|editor, window, cx| {
16771        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16772    });
16773
16774    cx.assert_editor_state(indoc! {"
16775        fn func(abc def: i32) -> ˇu32 {
16776        }
16777    "});
16778
16779    cx.update_editor(|editor, window, cx| {
16780        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16781    });
16782
16783    cx.assert_editor_state(indoc! {"
16784        fn func(abc ˇdef: i32) -> u32 {
16785        }
16786    "});
16787
16788    cx.update_editor(|editor, window, cx| {
16789        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16790    });
16791
16792    cx.assert_editor_state(indoc! {"
16793        fn func(abcˇ def: i32) -> u32 {
16794        }
16795    "});
16796
16797    cx.update_editor(|editor, window, cx| {
16798        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16799    });
16800
16801    cx.assert_editor_state(indoc! {"
16802        fn func(abc def: i32) -> ˇu32 {
16803        }
16804    "});
16805}
16806
16807#[gpui::test]
16808async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16809    init_test(cx, |_| {});
16810
16811    let mut cx = EditorTestContext::new(cx).await;
16812
16813    let diff_base = r#"
16814        use some::mod;
16815
16816        const A: u32 = 42;
16817
16818        fn main() {
16819            println!("hello");
16820
16821            println!("world");
16822        }
16823        "#
16824    .unindent();
16825
16826    // Edits are modified, removed, modified, added
16827    cx.set_state(
16828        &r#"
16829        use some::modified;
16830
16831        ˇ
16832        fn main() {
16833            println!("hello there");
16834
16835            println!("around the");
16836            println!("world");
16837        }
16838        "#
16839        .unindent(),
16840    );
16841
16842    cx.set_head_text(&diff_base);
16843    executor.run_until_parked();
16844
16845    cx.update_editor(|editor, window, cx| {
16846        //Wrap around the bottom of the buffer
16847        for _ in 0..3 {
16848            editor.go_to_next_hunk(&GoToHunk, window, cx);
16849        }
16850    });
16851
16852    cx.assert_editor_state(
16853        &r#"
16854        ˇuse some::modified;
16855
16856
16857        fn main() {
16858            println!("hello there");
16859
16860            println!("around the");
16861            println!("world");
16862        }
16863        "#
16864        .unindent(),
16865    );
16866
16867    cx.update_editor(|editor, window, cx| {
16868        //Wrap around the top of the buffer
16869        for _ in 0..2 {
16870            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16871        }
16872    });
16873
16874    cx.assert_editor_state(
16875        &r#"
16876        use some::modified;
16877
16878
16879        fn main() {
16880        ˇ    println!("hello there");
16881
16882            println!("around the");
16883            println!("world");
16884        }
16885        "#
16886        .unindent(),
16887    );
16888
16889    cx.update_editor(|editor, window, cx| {
16890        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16891    });
16892
16893    cx.assert_editor_state(
16894        &r#"
16895        use some::modified;
16896
16897        ˇ
16898        fn main() {
16899            println!("hello there");
16900
16901            println!("around the");
16902            println!("world");
16903        }
16904        "#
16905        .unindent(),
16906    );
16907
16908    cx.update_editor(|editor, window, cx| {
16909        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16910    });
16911
16912    cx.assert_editor_state(
16913        &r#"
16914        ˇuse some::modified;
16915
16916
16917        fn main() {
16918            println!("hello there");
16919
16920            println!("around the");
16921            println!("world");
16922        }
16923        "#
16924        .unindent(),
16925    );
16926
16927    cx.update_editor(|editor, window, cx| {
16928        for _ in 0..2 {
16929            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16930        }
16931    });
16932
16933    cx.assert_editor_state(
16934        &r#"
16935        use some::modified;
16936
16937
16938        fn main() {
16939        ˇ    println!("hello there");
16940
16941            println!("around the");
16942            println!("world");
16943        }
16944        "#
16945        .unindent(),
16946    );
16947
16948    cx.update_editor(|editor, window, cx| {
16949        editor.fold(&Fold, window, cx);
16950    });
16951
16952    cx.update_editor(|editor, window, cx| {
16953        editor.go_to_next_hunk(&GoToHunk, window, cx);
16954    });
16955
16956    cx.assert_editor_state(
16957        &r#"
16958        ˇuse some::modified;
16959
16960
16961        fn main() {
16962            println!("hello there");
16963
16964            println!("around the");
16965            println!("world");
16966        }
16967        "#
16968        .unindent(),
16969    );
16970}
16971
16972#[test]
16973fn test_split_words() {
16974    fn split(text: &str) -> Vec<&str> {
16975        split_words(text).collect()
16976    }
16977
16978    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16979    assert_eq!(split("hello_world"), &["hello_", "world"]);
16980    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16981    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16982    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16983    assert_eq!(split("helloworld"), &["helloworld"]);
16984
16985    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16986}
16987
16988#[gpui::test]
16989async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16990    init_test(cx, |_| {});
16991
16992    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16993    let mut assert = |before, after| {
16994        let _state_context = cx.set_state(before);
16995        cx.run_until_parked();
16996        cx.update_editor(|editor, window, cx| {
16997            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16998        });
16999        cx.run_until_parked();
17000        cx.assert_editor_state(after);
17001    };
17002
17003    // Outside bracket jumps to outside of matching bracket
17004    assert("console.logˇ(var);", "console.log(var)ˇ;");
17005    assert("console.log(var)ˇ;", "console.logˇ(var);");
17006
17007    // Inside bracket jumps to inside of matching bracket
17008    assert("console.log(ˇvar);", "console.log(varˇ);");
17009    assert("console.log(varˇ);", "console.log(ˇvar);");
17010
17011    // When outside a bracket and inside, favor jumping to the inside bracket
17012    assert(
17013        "console.log('foo', [1, 2, 3]ˇ);",
17014        "console.log(ˇ'foo', [1, 2, 3]);",
17015    );
17016    assert(
17017        "console.log(ˇ'foo', [1, 2, 3]);",
17018        "console.log('foo', [1, 2, 3]ˇ);",
17019    );
17020
17021    // Bias forward if two options are equally likely
17022    assert(
17023        "let result = curried_fun()ˇ();",
17024        "let result = curried_fun()()ˇ;",
17025    );
17026
17027    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17028    assert(
17029        indoc! {"
17030            function test() {
17031                console.log('test')ˇ
17032            }"},
17033        indoc! {"
17034            function test() {
17035                console.logˇ('test')
17036            }"},
17037    );
17038}
17039
17040#[gpui::test]
17041async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17042    init_test(cx, |_| {});
17043
17044    let fs = FakeFs::new(cx.executor());
17045    fs.insert_tree(
17046        path!("/a"),
17047        json!({
17048            "main.rs": "fn main() { let a = 5; }",
17049            "other.rs": "// Test file",
17050        }),
17051    )
17052    .await;
17053    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17054
17055    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17056    language_registry.add(Arc::new(Language::new(
17057        LanguageConfig {
17058            name: "Rust".into(),
17059            matcher: LanguageMatcher {
17060                path_suffixes: vec!["rs".to_string()],
17061                ..Default::default()
17062            },
17063            brackets: BracketPairConfig {
17064                pairs: vec![BracketPair {
17065                    start: "{".to_string(),
17066                    end: "}".to_string(),
17067                    close: true,
17068                    surround: true,
17069                    newline: true,
17070                }],
17071                disabled_scopes_by_bracket_ix: Vec::new(),
17072            },
17073            ..Default::default()
17074        },
17075        Some(tree_sitter_rust::LANGUAGE.into()),
17076    )));
17077    let mut fake_servers = language_registry.register_fake_lsp(
17078        "Rust",
17079        FakeLspAdapter {
17080            capabilities: lsp::ServerCapabilities {
17081                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17082                    first_trigger_character: "{".to_string(),
17083                    more_trigger_character: None,
17084                }),
17085                ..Default::default()
17086            },
17087            ..Default::default()
17088        },
17089    );
17090
17091    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17092
17093    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17094
17095    let worktree_id = workspace
17096        .update(cx, |workspace, _, cx| {
17097            workspace.project().update(cx, |project, cx| {
17098                project.worktrees(cx).next().unwrap().read(cx).id()
17099            })
17100        })
17101        .unwrap();
17102
17103    let buffer = project
17104        .update(cx, |project, cx| {
17105            project.open_local_buffer(path!("/a/main.rs"), cx)
17106        })
17107        .await
17108        .unwrap();
17109    let editor_handle = workspace
17110        .update(cx, |workspace, window, cx| {
17111            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17112        })
17113        .unwrap()
17114        .await
17115        .unwrap()
17116        .downcast::<Editor>()
17117        .unwrap();
17118
17119    cx.executor().start_waiting();
17120    let fake_server = fake_servers.next().await.unwrap();
17121
17122    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17123        |params, _| async move {
17124            assert_eq!(
17125                params.text_document_position.text_document.uri,
17126                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17127            );
17128            assert_eq!(
17129                params.text_document_position.position,
17130                lsp::Position::new(0, 21),
17131            );
17132
17133            Ok(Some(vec![lsp::TextEdit {
17134                new_text: "]".to_string(),
17135                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17136            }]))
17137        },
17138    );
17139
17140    editor_handle.update_in(cx, |editor, window, cx| {
17141        window.focus(&editor.focus_handle(cx));
17142        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17143            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17144        });
17145        editor.handle_input("{", window, cx);
17146    });
17147
17148    cx.executor().run_until_parked();
17149
17150    buffer.update(cx, |buffer, _| {
17151        assert_eq!(
17152            buffer.text(),
17153            "fn main() { let a = {5}; }",
17154            "No extra braces from on type formatting should appear in the buffer"
17155        )
17156    });
17157}
17158
17159#[gpui::test(iterations = 20, seeds(31))]
17160async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17161    init_test(cx, |_| {});
17162
17163    let mut cx = EditorLspTestContext::new_rust(
17164        lsp::ServerCapabilities {
17165            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17166                first_trigger_character: ".".to_string(),
17167                more_trigger_character: None,
17168            }),
17169            ..Default::default()
17170        },
17171        cx,
17172    )
17173    .await;
17174
17175    cx.update_buffer(|buffer, _| {
17176        // This causes autoindent to be async.
17177        buffer.set_sync_parse_timeout(Duration::ZERO)
17178    });
17179
17180    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17181    cx.simulate_keystroke("\n");
17182    cx.run_until_parked();
17183
17184    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17185    let mut request =
17186        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17187            let buffer_cloned = buffer_cloned.clone();
17188            async move {
17189                buffer_cloned.update(&mut cx, |buffer, _| {
17190                    assert_eq!(
17191                        buffer.text(),
17192                        "fn c() {\n    d()\n        .\n}\n",
17193                        "OnTypeFormatting should triggered after autoindent applied"
17194                    )
17195                })?;
17196
17197                Ok(Some(vec![]))
17198            }
17199        });
17200
17201    cx.simulate_keystroke(".");
17202    cx.run_until_parked();
17203
17204    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17205    assert!(request.next().await.is_some());
17206    request.close();
17207    assert!(request.next().await.is_none());
17208}
17209
17210#[gpui::test]
17211async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17212    init_test(cx, |_| {});
17213
17214    let fs = FakeFs::new(cx.executor());
17215    fs.insert_tree(
17216        path!("/a"),
17217        json!({
17218            "main.rs": "fn main() { let a = 5; }",
17219            "other.rs": "// Test file",
17220        }),
17221    )
17222    .await;
17223
17224    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17225
17226    let server_restarts = Arc::new(AtomicUsize::new(0));
17227    let closure_restarts = Arc::clone(&server_restarts);
17228    let language_server_name = "test language server";
17229    let language_name: LanguageName = "Rust".into();
17230
17231    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17232    language_registry.add(Arc::new(Language::new(
17233        LanguageConfig {
17234            name: language_name.clone(),
17235            matcher: LanguageMatcher {
17236                path_suffixes: vec!["rs".to_string()],
17237                ..Default::default()
17238            },
17239            ..Default::default()
17240        },
17241        Some(tree_sitter_rust::LANGUAGE.into()),
17242    )));
17243    let mut fake_servers = language_registry.register_fake_lsp(
17244        "Rust",
17245        FakeLspAdapter {
17246            name: language_server_name,
17247            initialization_options: Some(json!({
17248                "testOptionValue": true
17249            })),
17250            initializer: Some(Box::new(move |fake_server| {
17251                let task_restarts = Arc::clone(&closure_restarts);
17252                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17253                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17254                    futures::future::ready(Ok(()))
17255                });
17256            })),
17257            ..Default::default()
17258        },
17259    );
17260
17261    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17262    let _buffer = project
17263        .update(cx, |project, cx| {
17264            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17265        })
17266        .await
17267        .unwrap();
17268    let _fake_server = fake_servers.next().await.unwrap();
17269    update_test_language_settings(cx, |language_settings| {
17270        language_settings.languages.0.insert(
17271            language_name.clone().0,
17272            LanguageSettingsContent {
17273                tab_size: NonZeroU32::new(8),
17274                ..Default::default()
17275            },
17276        );
17277    });
17278    cx.executor().run_until_parked();
17279    assert_eq!(
17280        server_restarts.load(atomic::Ordering::Acquire),
17281        0,
17282        "Should not restart LSP server on an unrelated change"
17283    );
17284
17285    update_test_project_settings(cx, |project_settings| {
17286        project_settings.lsp.insert(
17287            "Some other server name".into(),
17288            LspSettings {
17289                binary: None,
17290                settings: None,
17291                initialization_options: Some(json!({
17292                    "some other init value": false
17293                })),
17294                enable_lsp_tasks: false,
17295                fetch: None,
17296            },
17297        );
17298    });
17299    cx.executor().run_until_parked();
17300    assert_eq!(
17301        server_restarts.load(atomic::Ordering::Acquire),
17302        0,
17303        "Should not restart LSP server on an unrelated LSP settings change"
17304    );
17305
17306    update_test_project_settings(cx, |project_settings| {
17307        project_settings.lsp.insert(
17308            language_server_name.into(),
17309            LspSettings {
17310                binary: None,
17311                settings: None,
17312                initialization_options: Some(json!({
17313                    "anotherInitValue": false
17314                })),
17315                enable_lsp_tasks: false,
17316                fetch: None,
17317            },
17318        );
17319    });
17320    cx.executor().run_until_parked();
17321    assert_eq!(
17322        server_restarts.load(atomic::Ordering::Acquire),
17323        1,
17324        "Should restart LSP server on a related LSP settings change"
17325    );
17326
17327    update_test_project_settings(cx, |project_settings| {
17328        project_settings.lsp.insert(
17329            language_server_name.into(),
17330            LspSettings {
17331                binary: None,
17332                settings: None,
17333                initialization_options: Some(json!({
17334                    "anotherInitValue": false
17335                })),
17336                enable_lsp_tasks: false,
17337                fetch: None,
17338            },
17339        );
17340    });
17341    cx.executor().run_until_parked();
17342    assert_eq!(
17343        server_restarts.load(atomic::Ordering::Acquire),
17344        1,
17345        "Should not restart LSP server on a related LSP settings change that is the same"
17346    );
17347
17348    update_test_project_settings(cx, |project_settings| {
17349        project_settings.lsp.insert(
17350            language_server_name.into(),
17351            LspSettings {
17352                binary: None,
17353                settings: None,
17354                initialization_options: None,
17355                enable_lsp_tasks: false,
17356                fetch: None,
17357            },
17358        );
17359    });
17360    cx.executor().run_until_parked();
17361    assert_eq!(
17362        server_restarts.load(atomic::Ordering::Acquire),
17363        2,
17364        "Should restart LSP server on another related LSP settings change"
17365    );
17366}
17367
17368#[gpui::test]
17369async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17370    init_test(cx, |_| {});
17371
17372    let mut cx = EditorLspTestContext::new_rust(
17373        lsp::ServerCapabilities {
17374            completion_provider: Some(lsp::CompletionOptions {
17375                trigger_characters: Some(vec![".".to_string()]),
17376                resolve_provider: Some(true),
17377                ..Default::default()
17378            }),
17379            ..Default::default()
17380        },
17381        cx,
17382    )
17383    .await;
17384
17385    cx.set_state("fn main() { let a = 2ˇ; }");
17386    cx.simulate_keystroke(".");
17387    let completion_item = lsp::CompletionItem {
17388        label: "some".into(),
17389        kind: Some(lsp::CompletionItemKind::SNIPPET),
17390        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17391        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17392            kind: lsp::MarkupKind::Markdown,
17393            value: "```rust\nSome(2)\n```".to_string(),
17394        })),
17395        deprecated: Some(false),
17396        sort_text: Some("fffffff2".to_string()),
17397        filter_text: Some("some".to_string()),
17398        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17399        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17400            range: lsp::Range {
17401                start: lsp::Position {
17402                    line: 0,
17403                    character: 22,
17404                },
17405                end: lsp::Position {
17406                    line: 0,
17407                    character: 22,
17408                },
17409            },
17410            new_text: "Some(2)".to_string(),
17411        })),
17412        additional_text_edits: Some(vec![lsp::TextEdit {
17413            range: lsp::Range {
17414                start: lsp::Position {
17415                    line: 0,
17416                    character: 20,
17417                },
17418                end: lsp::Position {
17419                    line: 0,
17420                    character: 22,
17421                },
17422            },
17423            new_text: "".to_string(),
17424        }]),
17425        ..Default::default()
17426    };
17427
17428    let closure_completion_item = completion_item.clone();
17429    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17430        let task_completion_item = closure_completion_item.clone();
17431        async move {
17432            Ok(Some(lsp::CompletionResponse::Array(vec![
17433                task_completion_item,
17434            ])))
17435        }
17436    });
17437
17438    request.next().await;
17439
17440    cx.condition(|editor, _| editor.context_menu_visible())
17441        .await;
17442    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17443        editor
17444            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17445            .unwrap()
17446    });
17447    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17448
17449    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17450        let task_completion_item = completion_item.clone();
17451        async move { Ok(task_completion_item) }
17452    })
17453    .next()
17454    .await
17455    .unwrap();
17456    apply_additional_edits.await.unwrap();
17457    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17458}
17459
17460#[gpui::test]
17461async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17462    init_test(cx, |_| {});
17463
17464    let mut cx = EditorLspTestContext::new_rust(
17465        lsp::ServerCapabilities {
17466            completion_provider: Some(lsp::CompletionOptions {
17467                trigger_characters: Some(vec![".".to_string()]),
17468                resolve_provider: Some(true),
17469                ..Default::default()
17470            }),
17471            ..Default::default()
17472        },
17473        cx,
17474    )
17475    .await;
17476
17477    cx.set_state("fn main() { let a = 2ˇ; }");
17478    cx.simulate_keystroke(".");
17479
17480    let item1 = lsp::CompletionItem {
17481        label: "method id()".to_string(),
17482        filter_text: Some("id".to_string()),
17483        detail: None,
17484        documentation: None,
17485        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17486            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17487            new_text: ".id".to_string(),
17488        })),
17489        ..lsp::CompletionItem::default()
17490    };
17491
17492    let item2 = lsp::CompletionItem {
17493        label: "other".to_string(),
17494        filter_text: Some("other".to_string()),
17495        detail: None,
17496        documentation: None,
17497        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17498            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17499            new_text: ".other".to_string(),
17500        })),
17501        ..lsp::CompletionItem::default()
17502    };
17503
17504    let item1 = item1.clone();
17505    cx.set_request_handler::<lsp::request::Completion, _, _>({
17506        let item1 = item1.clone();
17507        move |_, _, _| {
17508            let item1 = item1.clone();
17509            let item2 = item2.clone();
17510            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17511        }
17512    })
17513    .next()
17514    .await;
17515
17516    cx.condition(|editor, _| editor.context_menu_visible())
17517        .await;
17518    cx.update_editor(|editor, _, _| {
17519        let context_menu = editor.context_menu.borrow_mut();
17520        let context_menu = context_menu
17521            .as_ref()
17522            .expect("Should have the context menu deployed");
17523        match context_menu {
17524            CodeContextMenu::Completions(completions_menu) => {
17525                let completions = completions_menu.completions.borrow_mut();
17526                assert_eq!(
17527                    completions
17528                        .iter()
17529                        .map(|completion| &completion.label.text)
17530                        .collect::<Vec<_>>(),
17531                    vec!["method id()", "other"]
17532                )
17533            }
17534            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17535        }
17536    });
17537
17538    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17539        let item1 = item1.clone();
17540        move |_, item_to_resolve, _| {
17541            let item1 = item1.clone();
17542            async move {
17543                if item1 == item_to_resolve {
17544                    Ok(lsp::CompletionItem {
17545                        label: "method id()".to_string(),
17546                        filter_text: Some("id".to_string()),
17547                        detail: Some("Now resolved!".to_string()),
17548                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17549                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17550                            range: lsp::Range::new(
17551                                lsp::Position::new(0, 22),
17552                                lsp::Position::new(0, 22),
17553                            ),
17554                            new_text: ".id".to_string(),
17555                        })),
17556                        ..lsp::CompletionItem::default()
17557                    })
17558                } else {
17559                    Ok(item_to_resolve)
17560                }
17561            }
17562        }
17563    })
17564    .next()
17565    .await
17566    .unwrap();
17567    cx.run_until_parked();
17568
17569    cx.update_editor(|editor, window, cx| {
17570        editor.context_menu_next(&Default::default(), window, cx);
17571    });
17572
17573    cx.update_editor(|editor, _, _| {
17574        let context_menu = editor.context_menu.borrow_mut();
17575        let context_menu = context_menu
17576            .as_ref()
17577            .expect("Should have the context menu deployed");
17578        match context_menu {
17579            CodeContextMenu::Completions(completions_menu) => {
17580                let completions = completions_menu.completions.borrow_mut();
17581                assert_eq!(
17582                    completions
17583                        .iter()
17584                        .map(|completion| &completion.label.text)
17585                        .collect::<Vec<_>>(),
17586                    vec!["method id() Now resolved!", "other"],
17587                    "Should update first completion label, but not second as the filter text did not match."
17588                );
17589            }
17590            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17591        }
17592    });
17593}
17594
17595#[gpui::test]
17596async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17597    init_test(cx, |_| {});
17598    let mut cx = EditorLspTestContext::new_rust(
17599        lsp::ServerCapabilities {
17600            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17601            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17602            completion_provider: Some(lsp::CompletionOptions {
17603                resolve_provider: Some(true),
17604                ..Default::default()
17605            }),
17606            ..Default::default()
17607        },
17608        cx,
17609    )
17610    .await;
17611    cx.set_state(indoc! {"
17612        struct TestStruct {
17613            field: i32
17614        }
17615
17616        fn mainˇ() {
17617            let unused_var = 42;
17618            let test_struct = TestStruct { field: 42 };
17619        }
17620    "});
17621    let symbol_range = cx.lsp_range(indoc! {"
17622        struct TestStruct {
17623            field: i32
17624        }
17625
17626        «fn main»() {
17627            let unused_var = 42;
17628            let test_struct = TestStruct { field: 42 };
17629        }
17630    "});
17631    let mut hover_requests =
17632        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17633            Ok(Some(lsp::Hover {
17634                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17635                    kind: lsp::MarkupKind::Markdown,
17636                    value: "Function documentation".to_string(),
17637                }),
17638                range: Some(symbol_range),
17639            }))
17640        });
17641
17642    // Case 1: Test that code action menu hide hover popover
17643    cx.dispatch_action(Hover);
17644    hover_requests.next().await;
17645    cx.condition(|editor, _| editor.hover_state.visible()).await;
17646    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17647        move |_, _, _| async move {
17648            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17649                lsp::CodeAction {
17650                    title: "Remove unused variable".to_string(),
17651                    kind: Some(CodeActionKind::QUICKFIX),
17652                    edit: Some(lsp::WorkspaceEdit {
17653                        changes: Some(
17654                            [(
17655                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17656                                vec![lsp::TextEdit {
17657                                    range: lsp::Range::new(
17658                                        lsp::Position::new(5, 4),
17659                                        lsp::Position::new(5, 27),
17660                                    ),
17661                                    new_text: "".to_string(),
17662                                }],
17663                            )]
17664                            .into_iter()
17665                            .collect(),
17666                        ),
17667                        ..Default::default()
17668                    }),
17669                    ..Default::default()
17670                },
17671            )]))
17672        },
17673    );
17674    cx.update_editor(|editor, window, cx| {
17675        editor.toggle_code_actions(
17676            &ToggleCodeActions {
17677                deployed_from: None,
17678                quick_launch: false,
17679            },
17680            window,
17681            cx,
17682        );
17683    });
17684    code_action_requests.next().await;
17685    cx.run_until_parked();
17686    cx.condition(|editor, _| editor.context_menu_visible())
17687        .await;
17688    cx.update_editor(|editor, _, _| {
17689        assert!(
17690            !editor.hover_state.visible(),
17691            "Hover popover should be hidden when code action menu is shown"
17692        );
17693        // Hide code actions
17694        editor.context_menu.take();
17695    });
17696
17697    // Case 2: Test that code completions hide hover popover
17698    cx.dispatch_action(Hover);
17699    hover_requests.next().await;
17700    cx.condition(|editor, _| editor.hover_state.visible()).await;
17701    let counter = Arc::new(AtomicUsize::new(0));
17702    let mut completion_requests =
17703        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17704            let counter = counter.clone();
17705            async move {
17706                counter.fetch_add(1, atomic::Ordering::Release);
17707                Ok(Some(lsp::CompletionResponse::Array(vec![
17708                    lsp::CompletionItem {
17709                        label: "main".into(),
17710                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17711                        detail: Some("() -> ()".to_string()),
17712                        ..Default::default()
17713                    },
17714                    lsp::CompletionItem {
17715                        label: "TestStruct".into(),
17716                        kind: Some(lsp::CompletionItemKind::STRUCT),
17717                        detail: Some("struct TestStruct".to_string()),
17718                        ..Default::default()
17719                    },
17720                ])))
17721            }
17722        });
17723    cx.update_editor(|editor, window, cx| {
17724        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17725    });
17726    completion_requests.next().await;
17727    cx.condition(|editor, _| editor.context_menu_visible())
17728        .await;
17729    cx.update_editor(|editor, _, _| {
17730        assert!(
17731            !editor.hover_state.visible(),
17732            "Hover popover should be hidden when completion menu is shown"
17733        );
17734    });
17735}
17736
17737#[gpui::test]
17738async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17739    init_test(cx, |_| {});
17740
17741    let mut cx = EditorLspTestContext::new_rust(
17742        lsp::ServerCapabilities {
17743            completion_provider: Some(lsp::CompletionOptions {
17744                trigger_characters: Some(vec![".".to_string()]),
17745                resolve_provider: Some(true),
17746                ..Default::default()
17747            }),
17748            ..Default::default()
17749        },
17750        cx,
17751    )
17752    .await;
17753
17754    cx.set_state("fn main() { let a = 2ˇ; }");
17755    cx.simulate_keystroke(".");
17756
17757    let unresolved_item_1 = lsp::CompletionItem {
17758        label: "id".to_string(),
17759        filter_text: Some("id".to_string()),
17760        detail: None,
17761        documentation: None,
17762        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17763            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17764            new_text: ".id".to_string(),
17765        })),
17766        ..lsp::CompletionItem::default()
17767    };
17768    let resolved_item_1 = lsp::CompletionItem {
17769        additional_text_edits: Some(vec![lsp::TextEdit {
17770            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17771            new_text: "!!".to_string(),
17772        }]),
17773        ..unresolved_item_1.clone()
17774    };
17775    let unresolved_item_2 = lsp::CompletionItem {
17776        label: "other".to_string(),
17777        filter_text: Some("other".to_string()),
17778        detail: None,
17779        documentation: None,
17780        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17781            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17782            new_text: ".other".to_string(),
17783        })),
17784        ..lsp::CompletionItem::default()
17785    };
17786    let resolved_item_2 = lsp::CompletionItem {
17787        additional_text_edits: Some(vec![lsp::TextEdit {
17788            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17789            new_text: "??".to_string(),
17790        }]),
17791        ..unresolved_item_2.clone()
17792    };
17793
17794    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17795    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17796    cx.lsp
17797        .server
17798        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17799            let unresolved_item_1 = unresolved_item_1.clone();
17800            let resolved_item_1 = resolved_item_1.clone();
17801            let unresolved_item_2 = unresolved_item_2.clone();
17802            let resolved_item_2 = resolved_item_2.clone();
17803            let resolve_requests_1 = resolve_requests_1.clone();
17804            let resolve_requests_2 = resolve_requests_2.clone();
17805            move |unresolved_request, _| {
17806                let unresolved_item_1 = unresolved_item_1.clone();
17807                let resolved_item_1 = resolved_item_1.clone();
17808                let unresolved_item_2 = unresolved_item_2.clone();
17809                let resolved_item_2 = resolved_item_2.clone();
17810                let resolve_requests_1 = resolve_requests_1.clone();
17811                let resolve_requests_2 = resolve_requests_2.clone();
17812                async move {
17813                    if unresolved_request == unresolved_item_1 {
17814                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17815                        Ok(resolved_item_1.clone())
17816                    } else if unresolved_request == unresolved_item_2 {
17817                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17818                        Ok(resolved_item_2.clone())
17819                    } else {
17820                        panic!("Unexpected completion item {unresolved_request:?}")
17821                    }
17822                }
17823            }
17824        })
17825        .detach();
17826
17827    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17828        let unresolved_item_1 = unresolved_item_1.clone();
17829        let unresolved_item_2 = unresolved_item_2.clone();
17830        async move {
17831            Ok(Some(lsp::CompletionResponse::Array(vec![
17832                unresolved_item_1,
17833                unresolved_item_2,
17834            ])))
17835        }
17836    })
17837    .next()
17838    .await;
17839
17840    cx.condition(|editor, _| editor.context_menu_visible())
17841        .await;
17842    cx.update_editor(|editor, _, _| {
17843        let context_menu = editor.context_menu.borrow_mut();
17844        let context_menu = context_menu
17845            .as_ref()
17846            .expect("Should have the context menu deployed");
17847        match context_menu {
17848            CodeContextMenu::Completions(completions_menu) => {
17849                let completions = completions_menu.completions.borrow_mut();
17850                assert_eq!(
17851                    completions
17852                        .iter()
17853                        .map(|completion| &completion.label.text)
17854                        .collect::<Vec<_>>(),
17855                    vec!["id", "other"]
17856                )
17857            }
17858            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17859        }
17860    });
17861    cx.run_until_parked();
17862
17863    cx.update_editor(|editor, window, cx| {
17864        editor.context_menu_next(&ContextMenuNext, window, cx);
17865    });
17866    cx.run_until_parked();
17867    cx.update_editor(|editor, window, cx| {
17868        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17869    });
17870    cx.run_until_parked();
17871    cx.update_editor(|editor, window, cx| {
17872        editor.context_menu_next(&ContextMenuNext, window, cx);
17873    });
17874    cx.run_until_parked();
17875    cx.update_editor(|editor, window, cx| {
17876        editor
17877            .compose_completion(&ComposeCompletion::default(), window, cx)
17878            .expect("No task returned")
17879    })
17880    .await
17881    .expect("Completion failed");
17882    cx.run_until_parked();
17883
17884    cx.update_editor(|editor, _, cx| {
17885        assert_eq!(
17886            resolve_requests_1.load(atomic::Ordering::Acquire),
17887            1,
17888            "Should always resolve once despite multiple selections"
17889        );
17890        assert_eq!(
17891            resolve_requests_2.load(atomic::Ordering::Acquire),
17892            1,
17893            "Should always resolve once after multiple selections and applying the completion"
17894        );
17895        assert_eq!(
17896            editor.text(cx),
17897            "fn main() { let a = ??.other; }",
17898            "Should use resolved data when applying the completion"
17899        );
17900    });
17901}
17902
17903#[gpui::test]
17904async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17905    init_test(cx, |_| {});
17906
17907    let item_0 = lsp::CompletionItem {
17908        label: "abs".into(),
17909        insert_text: Some("abs".into()),
17910        data: Some(json!({ "very": "special"})),
17911        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17912        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17913            lsp::InsertReplaceEdit {
17914                new_text: "abs".to_string(),
17915                insert: lsp::Range::default(),
17916                replace: lsp::Range::default(),
17917            },
17918        )),
17919        ..lsp::CompletionItem::default()
17920    };
17921    let items = iter::once(item_0.clone())
17922        .chain((11..51).map(|i| lsp::CompletionItem {
17923            label: format!("item_{}", i),
17924            insert_text: Some(format!("item_{}", i)),
17925            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17926            ..lsp::CompletionItem::default()
17927        }))
17928        .collect::<Vec<_>>();
17929
17930    let default_commit_characters = vec!["?".to_string()];
17931    let default_data = json!({ "default": "data"});
17932    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17933    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17934    let default_edit_range = lsp::Range {
17935        start: lsp::Position {
17936            line: 0,
17937            character: 5,
17938        },
17939        end: lsp::Position {
17940            line: 0,
17941            character: 5,
17942        },
17943    };
17944
17945    let mut cx = EditorLspTestContext::new_rust(
17946        lsp::ServerCapabilities {
17947            completion_provider: Some(lsp::CompletionOptions {
17948                trigger_characters: Some(vec![".".to_string()]),
17949                resolve_provider: Some(true),
17950                ..Default::default()
17951            }),
17952            ..Default::default()
17953        },
17954        cx,
17955    )
17956    .await;
17957
17958    cx.set_state("fn main() { let a = 2ˇ; }");
17959    cx.simulate_keystroke(".");
17960
17961    let completion_data = default_data.clone();
17962    let completion_characters = default_commit_characters.clone();
17963    let completion_items = items.clone();
17964    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17965        let default_data = completion_data.clone();
17966        let default_commit_characters = completion_characters.clone();
17967        let items = completion_items.clone();
17968        async move {
17969            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17970                items,
17971                item_defaults: Some(lsp::CompletionListItemDefaults {
17972                    data: Some(default_data.clone()),
17973                    commit_characters: Some(default_commit_characters.clone()),
17974                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17975                        default_edit_range,
17976                    )),
17977                    insert_text_format: Some(default_insert_text_format),
17978                    insert_text_mode: Some(default_insert_text_mode),
17979                }),
17980                ..lsp::CompletionList::default()
17981            })))
17982        }
17983    })
17984    .next()
17985    .await;
17986
17987    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17988    cx.lsp
17989        .server
17990        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17991            let closure_resolved_items = resolved_items.clone();
17992            move |item_to_resolve, _| {
17993                let closure_resolved_items = closure_resolved_items.clone();
17994                async move {
17995                    closure_resolved_items.lock().push(item_to_resolve.clone());
17996                    Ok(item_to_resolve)
17997                }
17998            }
17999        })
18000        .detach();
18001
18002    cx.condition(|editor, _| editor.context_menu_visible())
18003        .await;
18004    cx.run_until_parked();
18005    cx.update_editor(|editor, _, _| {
18006        let menu = editor.context_menu.borrow_mut();
18007        match menu.as_ref().expect("should have the completions menu") {
18008            CodeContextMenu::Completions(completions_menu) => {
18009                assert_eq!(
18010                    completions_menu
18011                        .entries
18012                        .borrow()
18013                        .iter()
18014                        .map(|mat| mat.string.clone())
18015                        .collect::<Vec<String>>(),
18016                    items
18017                        .iter()
18018                        .map(|completion| completion.label.clone())
18019                        .collect::<Vec<String>>()
18020                );
18021            }
18022            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18023        }
18024    });
18025    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18026    // with 4 from the end.
18027    assert_eq!(
18028        *resolved_items.lock(),
18029        [&items[0..16], &items[items.len() - 4..items.len()]]
18030            .concat()
18031            .iter()
18032            .cloned()
18033            .map(|mut item| {
18034                if item.data.is_none() {
18035                    item.data = Some(default_data.clone());
18036                }
18037                item
18038            })
18039            .collect::<Vec<lsp::CompletionItem>>(),
18040        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18041    );
18042    resolved_items.lock().clear();
18043
18044    cx.update_editor(|editor, window, cx| {
18045        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18046    });
18047    cx.run_until_parked();
18048    // Completions that have already been resolved are skipped.
18049    assert_eq!(
18050        *resolved_items.lock(),
18051        items[items.len() - 17..items.len() - 4]
18052            .iter()
18053            .cloned()
18054            .map(|mut item| {
18055                if item.data.is_none() {
18056                    item.data = Some(default_data.clone());
18057                }
18058                item
18059            })
18060            .collect::<Vec<lsp::CompletionItem>>()
18061    );
18062    resolved_items.lock().clear();
18063}
18064
18065#[gpui::test]
18066async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18067    init_test(cx, |_| {});
18068
18069    let mut cx = EditorLspTestContext::new(
18070        Language::new(
18071            LanguageConfig {
18072                matcher: LanguageMatcher {
18073                    path_suffixes: vec!["jsx".into()],
18074                    ..Default::default()
18075                },
18076                overrides: [(
18077                    "element".into(),
18078                    LanguageConfigOverride {
18079                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18080                        ..Default::default()
18081                    },
18082                )]
18083                .into_iter()
18084                .collect(),
18085                ..Default::default()
18086            },
18087            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18088        )
18089        .with_override_query("(jsx_self_closing_element) @element")
18090        .unwrap(),
18091        lsp::ServerCapabilities {
18092            completion_provider: Some(lsp::CompletionOptions {
18093                trigger_characters: Some(vec![":".to_string()]),
18094                ..Default::default()
18095            }),
18096            ..Default::default()
18097        },
18098        cx,
18099    )
18100    .await;
18101
18102    cx.lsp
18103        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18104            Ok(Some(lsp::CompletionResponse::Array(vec![
18105                lsp::CompletionItem {
18106                    label: "bg-blue".into(),
18107                    ..Default::default()
18108                },
18109                lsp::CompletionItem {
18110                    label: "bg-red".into(),
18111                    ..Default::default()
18112                },
18113                lsp::CompletionItem {
18114                    label: "bg-yellow".into(),
18115                    ..Default::default()
18116                },
18117            ])))
18118        });
18119
18120    cx.set_state(r#"<p class="bgˇ" />"#);
18121
18122    // Trigger completion when typing a dash, because the dash is an extra
18123    // word character in the 'element' scope, which contains the cursor.
18124    cx.simulate_keystroke("-");
18125    cx.executor().run_until_parked();
18126    cx.update_editor(|editor, _, _| {
18127        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18128        {
18129            assert_eq!(
18130                completion_menu_entries(menu),
18131                &["bg-blue", "bg-red", "bg-yellow"]
18132            );
18133        } else {
18134            panic!("expected completion menu to be open");
18135        }
18136    });
18137
18138    cx.simulate_keystroke("l");
18139    cx.executor().run_until_parked();
18140    cx.update_editor(|editor, _, _| {
18141        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18142        {
18143            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18144        } else {
18145            panic!("expected completion menu to be open");
18146        }
18147    });
18148
18149    // When filtering completions, consider the character after the '-' to
18150    // be the start of a subword.
18151    cx.set_state(r#"<p class="yelˇ" />"#);
18152    cx.simulate_keystroke("l");
18153    cx.executor().run_until_parked();
18154    cx.update_editor(|editor, _, _| {
18155        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18156        {
18157            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18158        } else {
18159            panic!("expected completion menu to be open");
18160        }
18161    });
18162}
18163
18164fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18165    let entries = menu.entries.borrow();
18166    entries.iter().map(|mat| mat.string.clone()).collect()
18167}
18168
18169#[gpui::test]
18170async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18171    init_test(cx, |settings| {
18172        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18173            Formatter::Prettier,
18174        )))
18175    });
18176
18177    let fs = FakeFs::new(cx.executor());
18178    fs.insert_file(path!("/file.ts"), Default::default()).await;
18179
18180    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18181    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18182
18183    language_registry.add(Arc::new(Language::new(
18184        LanguageConfig {
18185            name: "TypeScript".into(),
18186            matcher: LanguageMatcher {
18187                path_suffixes: vec!["ts".to_string()],
18188                ..Default::default()
18189            },
18190            ..Default::default()
18191        },
18192        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18193    )));
18194    update_test_language_settings(cx, |settings| {
18195        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18196    });
18197
18198    let test_plugin = "test_plugin";
18199    let _ = language_registry.register_fake_lsp(
18200        "TypeScript",
18201        FakeLspAdapter {
18202            prettier_plugins: vec![test_plugin],
18203            ..Default::default()
18204        },
18205    );
18206
18207    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18208    let buffer = project
18209        .update(cx, |project, cx| {
18210            project.open_local_buffer(path!("/file.ts"), cx)
18211        })
18212        .await
18213        .unwrap();
18214
18215    let buffer_text = "one\ntwo\nthree\n";
18216    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18217    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18218    editor.update_in(cx, |editor, window, cx| {
18219        editor.set_text(buffer_text, window, cx)
18220    });
18221
18222    editor
18223        .update_in(cx, |editor, window, cx| {
18224            editor.perform_format(
18225                project.clone(),
18226                FormatTrigger::Manual,
18227                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18228                window,
18229                cx,
18230            )
18231        })
18232        .unwrap()
18233        .await;
18234    assert_eq!(
18235        editor.update(cx, |editor, cx| editor.text(cx)),
18236        buffer_text.to_string() + prettier_format_suffix,
18237        "Test prettier formatting was not applied to the original buffer text",
18238    );
18239
18240    update_test_language_settings(cx, |settings| {
18241        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18242    });
18243    let format = editor.update_in(cx, |editor, window, cx| {
18244        editor.perform_format(
18245            project.clone(),
18246            FormatTrigger::Manual,
18247            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18248            window,
18249            cx,
18250        )
18251    });
18252    format.await.unwrap();
18253    assert_eq!(
18254        editor.update(cx, |editor, cx| editor.text(cx)),
18255        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18256        "Autoformatting (via test prettier) was not applied to the original buffer text",
18257    );
18258}
18259
18260#[gpui::test]
18261async fn test_addition_reverts(cx: &mut TestAppContext) {
18262    init_test(cx, |_| {});
18263    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18264    let base_text = indoc! {r#"
18265        struct Row;
18266        struct Row1;
18267        struct Row2;
18268
18269        struct Row4;
18270        struct Row5;
18271        struct Row6;
18272
18273        struct Row8;
18274        struct Row9;
18275        struct Row10;"#};
18276
18277    // When addition hunks are not adjacent to carets, no hunk revert is performed
18278    assert_hunk_revert(
18279        indoc! {r#"struct Row;
18280                   struct Row1;
18281                   struct Row1.1;
18282                   struct Row1.2;
18283                   struct Row2;ˇ
18284
18285                   struct Row4;
18286                   struct Row5;
18287                   struct Row6;
18288
18289                   struct Row8;
18290                   ˇstruct Row9;
18291                   struct Row9.1;
18292                   struct Row9.2;
18293                   struct Row9.3;
18294                   struct Row10;"#},
18295        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18296        indoc! {r#"struct Row;
18297                   struct Row1;
18298                   struct Row1.1;
18299                   struct Row1.2;
18300                   struct Row2;ˇ
18301
18302                   struct Row4;
18303                   struct Row5;
18304                   struct Row6;
18305
18306                   struct Row8;
18307                   ˇstruct Row9;
18308                   struct Row9.1;
18309                   struct Row9.2;
18310                   struct Row9.3;
18311                   struct Row10;"#},
18312        base_text,
18313        &mut cx,
18314    );
18315    // Same for selections
18316    assert_hunk_revert(
18317        indoc! {r#"struct Row;
18318                   struct Row1;
18319                   struct Row2;
18320                   struct Row2.1;
18321                   struct Row2.2;
18322                   «ˇ
18323                   struct Row4;
18324                   struct» Row5;
18325                   «struct Row6;
18326                   ˇ»
18327                   struct Row9.1;
18328                   struct Row9.2;
18329                   struct Row9.3;
18330                   struct Row8;
18331                   struct Row9;
18332                   struct Row10;"#},
18333        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18334        indoc! {r#"struct Row;
18335                   struct Row1;
18336                   struct Row2;
18337                   struct Row2.1;
18338                   struct Row2.2;
18339                   «ˇ
18340                   struct Row4;
18341                   struct» Row5;
18342                   «struct Row6;
18343                   ˇ»
18344                   struct Row9.1;
18345                   struct Row9.2;
18346                   struct Row9.3;
18347                   struct Row8;
18348                   struct Row9;
18349                   struct Row10;"#},
18350        base_text,
18351        &mut cx,
18352    );
18353
18354    // When carets and selections intersect the addition hunks, those are reverted.
18355    // Adjacent carets got merged.
18356    assert_hunk_revert(
18357        indoc! {r#"struct Row;
18358                   ˇ// something on the top
18359                   struct Row1;
18360                   struct Row2;
18361                   struct Roˇw3.1;
18362                   struct Row2.2;
18363                   struct Row2.3;ˇ
18364
18365                   struct Row4;
18366                   struct ˇRow5.1;
18367                   struct Row5.2;
18368                   struct «Rowˇ»5.3;
18369                   struct Row5;
18370                   struct Row6;
18371                   ˇ
18372                   struct Row9.1;
18373                   struct «Rowˇ»9.2;
18374                   struct «ˇRow»9.3;
18375                   struct Row8;
18376                   struct Row9;
18377                   «ˇ// something on bottom»
18378                   struct Row10;"#},
18379        vec![
18380            DiffHunkStatusKind::Added,
18381            DiffHunkStatusKind::Added,
18382            DiffHunkStatusKind::Added,
18383            DiffHunkStatusKind::Added,
18384            DiffHunkStatusKind::Added,
18385        ],
18386        indoc! {r#"struct Row;
18387                   ˇstruct Row1;
18388                   struct Row2;
18389                   ˇ
18390                   struct Row4;
18391                   ˇstruct Row5;
18392                   struct Row6;
18393                   ˇ
18394                   ˇstruct Row8;
18395                   struct Row9;
18396                   ˇstruct Row10;"#},
18397        base_text,
18398        &mut cx,
18399    );
18400}
18401
18402#[gpui::test]
18403async fn test_modification_reverts(cx: &mut TestAppContext) {
18404    init_test(cx, |_| {});
18405    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18406    let base_text = indoc! {r#"
18407        struct Row;
18408        struct Row1;
18409        struct Row2;
18410
18411        struct Row4;
18412        struct Row5;
18413        struct Row6;
18414
18415        struct Row8;
18416        struct Row9;
18417        struct Row10;"#};
18418
18419    // Modification hunks behave the same as the addition ones.
18420    assert_hunk_revert(
18421        indoc! {r#"struct Row;
18422                   struct Row1;
18423                   struct Row33;
18424                   ˇ
18425                   struct Row4;
18426                   struct Row5;
18427                   struct Row6;
18428                   ˇ
18429                   struct Row99;
18430                   struct Row9;
18431                   struct Row10;"#},
18432        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18433        indoc! {r#"struct Row;
18434                   struct Row1;
18435                   struct Row33;
18436                   ˇ
18437                   struct Row4;
18438                   struct Row5;
18439                   struct Row6;
18440                   ˇ
18441                   struct Row99;
18442                   struct Row9;
18443                   struct Row10;"#},
18444        base_text,
18445        &mut cx,
18446    );
18447    assert_hunk_revert(
18448        indoc! {r#"struct Row;
18449                   struct Row1;
18450                   struct Row33;
18451                   «ˇ
18452                   struct Row4;
18453                   struct» Row5;
18454                   «struct Row6;
18455                   ˇ»
18456                   struct Row99;
18457                   struct Row9;
18458                   struct Row10;"#},
18459        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18460        indoc! {r#"struct Row;
18461                   struct Row1;
18462                   struct Row33;
18463                   «ˇ
18464                   struct Row4;
18465                   struct» Row5;
18466                   «struct Row6;
18467                   ˇ»
18468                   struct Row99;
18469                   struct Row9;
18470                   struct Row10;"#},
18471        base_text,
18472        &mut cx,
18473    );
18474
18475    assert_hunk_revert(
18476        indoc! {r#"ˇstruct Row1.1;
18477                   struct Row1;
18478                   «ˇstr»uct Row22;
18479
18480                   struct ˇRow44;
18481                   struct Row5;
18482                   struct «Rˇ»ow66;ˇ
18483
18484                   «struˇ»ct Row88;
18485                   struct Row9;
18486                   struct Row1011;ˇ"#},
18487        vec![
18488            DiffHunkStatusKind::Modified,
18489            DiffHunkStatusKind::Modified,
18490            DiffHunkStatusKind::Modified,
18491            DiffHunkStatusKind::Modified,
18492            DiffHunkStatusKind::Modified,
18493            DiffHunkStatusKind::Modified,
18494        ],
18495        indoc! {r#"struct Row;
18496                   ˇstruct Row1;
18497                   struct Row2;
18498                   ˇ
18499                   struct Row4;
18500                   ˇstruct Row5;
18501                   struct Row6;
18502                   ˇ
18503                   struct Row8;
18504                   ˇstruct Row9;
18505                   struct Row10;ˇ"#},
18506        base_text,
18507        &mut cx,
18508    );
18509}
18510
18511#[gpui::test]
18512async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18513    init_test(cx, |_| {});
18514    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18515    let base_text = indoc! {r#"
18516        one
18517
18518        two
18519        three
18520        "#};
18521
18522    cx.set_head_text(base_text);
18523    cx.set_state("\nˇ\n");
18524    cx.executor().run_until_parked();
18525    cx.update_editor(|editor, _window, cx| {
18526        editor.expand_selected_diff_hunks(cx);
18527    });
18528    cx.executor().run_until_parked();
18529    cx.update_editor(|editor, window, cx| {
18530        editor.backspace(&Default::default(), window, cx);
18531    });
18532    cx.run_until_parked();
18533    cx.assert_state_with_diff(
18534        indoc! {r#"
18535
18536        - two
18537        - threeˇ
18538        +
18539        "#}
18540        .to_string(),
18541    );
18542}
18543
18544#[gpui::test]
18545async fn test_deletion_reverts(cx: &mut TestAppContext) {
18546    init_test(cx, |_| {});
18547    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18548    let base_text = indoc! {r#"struct Row;
18549struct Row1;
18550struct Row2;
18551
18552struct Row4;
18553struct Row5;
18554struct Row6;
18555
18556struct Row8;
18557struct Row9;
18558struct Row10;"#};
18559
18560    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18561    assert_hunk_revert(
18562        indoc! {r#"struct Row;
18563                   struct Row2;
18564
18565                   ˇstruct Row4;
18566                   struct Row5;
18567                   struct Row6;
18568                   ˇ
18569                   struct Row8;
18570                   struct Row10;"#},
18571        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18572        indoc! {r#"struct Row;
18573                   struct Row2;
18574
18575                   ˇstruct Row4;
18576                   struct Row5;
18577                   struct Row6;
18578                   ˇ
18579                   struct Row8;
18580                   struct Row10;"#},
18581        base_text,
18582        &mut cx,
18583    );
18584    assert_hunk_revert(
18585        indoc! {r#"struct Row;
18586                   struct Row2;
18587
18588                   «ˇstruct Row4;
18589                   struct» Row5;
18590                   «struct Row6;
18591                   ˇ»
18592                   struct Row8;
18593                   struct Row10;"#},
18594        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18595        indoc! {r#"struct Row;
18596                   struct Row2;
18597
18598                   «ˇstruct Row4;
18599                   struct» Row5;
18600                   «struct Row6;
18601                   ˇ»
18602                   struct Row8;
18603                   struct Row10;"#},
18604        base_text,
18605        &mut cx,
18606    );
18607
18608    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18609    assert_hunk_revert(
18610        indoc! {r#"struct Row;
18611                   ˇstruct Row2;
18612
18613                   struct Row4;
18614                   struct Row5;
18615                   struct Row6;
18616
18617                   struct Row8;ˇ
18618                   struct Row10;"#},
18619        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18620        indoc! {r#"struct Row;
18621                   struct Row1;
18622                   ˇstruct Row2;
18623
18624                   struct Row4;
18625                   struct Row5;
18626                   struct Row6;
18627
18628                   struct Row8;ˇ
18629                   struct Row9;
18630                   struct Row10;"#},
18631        base_text,
18632        &mut cx,
18633    );
18634    assert_hunk_revert(
18635        indoc! {r#"struct Row;
18636                   struct Row2«ˇ;
18637                   struct Row4;
18638                   struct» Row5;
18639                   «struct Row6;
18640
18641                   struct Row8;ˇ»
18642                   struct Row10;"#},
18643        vec![
18644            DiffHunkStatusKind::Deleted,
18645            DiffHunkStatusKind::Deleted,
18646            DiffHunkStatusKind::Deleted,
18647        ],
18648        indoc! {r#"struct Row;
18649                   struct Row1;
18650                   struct Row2«ˇ;
18651
18652                   struct Row4;
18653                   struct» Row5;
18654                   «struct Row6;
18655
18656                   struct Row8;ˇ»
18657                   struct Row9;
18658                   struct Row10;"#},
18659        base_text,
18660        &mut cx,
18661    );
18662}
18663
18664#[gpui::test]
18665async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18666    init_test(cx, |_| {});
18667
18668    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18669    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18670    let base_text_3 =
18671        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18672
18673    let text_1 = edit_first_char_of_every_line(base_text_1);
18674    let text_2 = edit_first_char_of_every_line(base_text_2);
18675    let text_3 = edit_first_char_of_every_line(base_text_3);
18676
18677    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18678    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18679    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18680
18681    let multibuffer = cx.new(|cx| {
18682        let mut multibuffer = MultiBuffer::new(ReadWrite);
18683        multibuffer.push_excerpts(
18684            buffer_1.clone(),
18685            [
18686                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18687                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18688                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18689            ],
18690            cx,
18691        );
18692        multibuffer.push_excerpts(
18693            buffer_2.clone(),
18694            [
18695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18698            ],
18699            cx,
18700        );
18701        multibuffer.push_excerpts(
18702            buffer_3.clone(),
18703            [
18704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18707            ],
18708            cx,
18709        );
18710        multibuffer
18711    });
18712
18713    let fs = FakeFs::new(cx.executor());
18714    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18715    let (editor, cx) = cx
18716        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18717    editor.update_in(cx, |editor, _window, cx| {
18718        for (buffer, diff_base) in [
18719            (buffer_1.clone(), base_text_1),
18720            (buffer_2.clone(), base_text_2),
18721            (buffer_3.clone(), base_text_3),
18722        ] {
18723            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18724            editor
18725                .buffer
18726                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18727        }
18728    });
18729    cx.executor().run_until_parked();
18730
18731    editor.update_in(cx, |editor, window, cx| {
18732        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}");
18733        editor.select_all(&SelectAll, window, cx);
18734        editor.git_restore(&Default::default(), window, cx);
18735    });
18736    cx.executor().run_until_parked();
18737
18738    // When all ranges are selected, all buffer hunks are reverted.
18739    editor.update(cx, |editor, cx| {
18740        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");
18741    });
18742    buffer_1.update(cx, |buffer, _| {
18743        assert_eq!(buffer.text(), base_text_1);
18744    });
18745    buffer_2.update(cx, |buffer, _| {
18746        assert_eq!(buffer.text(), base_text_2);
18747    });
18748    buffer_3.update(cx, |buffer, _| {
18749        assert_eq!(buffer.text(), base_text_3);
18750    });
18751
18752    editor.update_in(cx, |editor, window, cx| {
18753        editor.undo(&Default::default(), window, cx);
18754    });
18755
18756    editor.update_in(cx, |editor, window, cx| {
18757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18758            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18759        });
18760        editor.git_restore(&Default::default(), window, cx);
18761    });
18762
18763    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18764    // but not affect buffer_2 and its related excerpts.
18765    editor.update(cx, |editor, cx| {
18766        assert_eq!(
18767            editor.text(cx),
18768            "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}"
18769        );
18770    });
18771    buffer_1.update(cx, |buffer, _| {
18772        assert_eq!(buffer.text(), base_text_1);
18773    });
18774    buffer_2.update(cx, |buffer, _| {
18775        assert_eq!(
18776            buffer.text(),
18777            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18778        );
18779    });
18780    buffer_3.update(cx, |buffer, _| {
18781        assert_eq!(
18782            buffer.text(),
18783            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18784        );
18785    });
18786
18787    fn edit_first_char_of_every_line(text: &str) -> String {
18788        text.split('\n')
18789            .map(|line| format!("X{}", &line[1..]))
18790            .collect::<Vec<_>>()
18791            .join("\n")
18792    }
18793}
18794
18795#[gpui::test]
18796async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18797    init_test(cx, |_| {});
18798
18799    let cols = 4;
18800    let rows = 10;
18801    let sample_text_1 = sample_text(rows, cols, 'a');
18802    assert_eq!(
18803        sample_text_1,
18804        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18805    );
18806    let sample_text_2 = sample_text(rows, cols, 'l');
18807    assert_eq!(
18808        sample_text_2,
18809        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18810    );
18811    let sample_text_3 = sample_text(rows, cols, 'v');
18812    assert_eq!(
18813        sample_text_3,
18814        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18815    );
18816
18817    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18818    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18819    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18820
18821    let multi_buffer = cx.new(|cx| {
18822        let mut multibuffer = MultiBuffer::new(ReadWrite);
18823        multibuffer.push_excerpts(
18824            buffer_1.clone(),
18825            [
18826                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18827                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18828                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18829            ],
18830            cx,
18831        );
18832        multibuffer.push_excerpts(
18833            buffer_2.clone(),
18834            [
18835                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18836                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18837                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18838            ],
18839            cx,
18840        );
18841        multibuffer.push_excerpts(
18842            buffer_3.clone(),
18843            [
18844                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18845                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18846                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18847            ],
18848            cx,
18849        );
18850        multibuffer
18851    });
18852
18853    let fs = FakeFs::new(cx.executor());
18854    fs.insert_tree(
18855        "/a",
18856        json!({
18857            "main.rs": sample_text_1,
18858            "other.rs": sample_text_2,
18859            "lib.rs": sample_text_3,
18860        }),
18861    )
18862    .await;
18863    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18864    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18865    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18866    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18867        Editor::new(
18868            EditorMode::full(),
18869            multi_buffer,
18870            Some(project.clone()),
18871            window,
18872            cx,
18873        )
18874    });
18875    let multibuffer_item_id = workspace
18876        .update(cx, |workspace, window, cx| {
18877            assert!(
18878                workspace.active_item(cx).is_none(),
18879                "active item should be None before the first item is added"
18880            );
18881            workspace.add_item_to_active_pane(
18882                Box::new(multi_buffer_editor.clone()),
18883                None,
18884                true,
18885                window,
18886                cx,
18887            );
18888            let active_item = workspace
18889                .active_item(cx)
18890                .expect("should have an active item after adding the multi buffer");
18891            assert_eq!(
18892                active_item.buffer_kind(cx),
18893                ItemBufferKind::Multibuffer,
18894                "A multi buffer was expected to active after adding"
18895            );
18896            active_item.item_id()
18897        })
18898        .unwrap();
18899    cx.executor().run_until_parked();
18900
18901    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18902        editor.change_selections(
18903            SelectionEffects::scroll(Autoscroll::Next),
18904            window,
18905            cx,
18906            |s| s.select_ranges(Some(1..2)),
18907        );
18908        editor.open_excerpts(&OpenExcerpts, window, cx);
18909    });
18910    cx.executor().run_until_parked();
18911    let first_item_id = workspace
18912        .update(cx, |workspace, window, cx| {
18913            let active_item = workspace
18914                .active_item(cx)
18915                .expect("should have an active item after navigating into the 1st buffer");
18916            let first_item_id = active_item.item_id();
18917            assert_ne!(
18918                first_item_id, multibuffer_item_id,
18919                "Should navigate into the 1st buffer and activate it"
18920            );
18921            assert_eq!(
18922                active_item.buffer_kind(cx),
18923                ItemBufferKind::Singleton,
18924                "New active item should be a singleton buffer"
18925            );
18926            assert_eq!(
18927                active_item
18928                    .act_as::<Editor>(cx)
18929                    .expect("should have navigated into an editor for the 1st buffer")
18930                    .read(cx)
18931                    .text(cx),
18932                sample_text_1
18933            );
18934
18935            workspace
18936                .go_back(workspace.active_pane().downgrade(), window, cx)
18937                .detach_and_log_err(cx);
18938
18939            first_item_id
18940        })
18941        .unwrap();
18942    cx.executor().run_until_parked();
18943    workspace
18944        .update(cx, |workspace, _, cx| {
18945            let active_item = workspace
18946                .active_item(cx)
18947                .expect("should have an active item after navigating back");
18948            assert_eq!(
18949                active_item.item_id(),
18950                multibuffer_item_id,
18951                "Should navigate back to the multi buffer"
18952            );
18953            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18954        })
18955        .unwrap();
18956
18957    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18958        editor.change_selections(
18959            SelectionEffects::scroll(Autoscroll::Next),
18960            window,
18961            cx,
18962            |s| s.select_ranges(Some(39..40)),
18963        );
18964        editor.open_excerpts(&OpenExcerpts, window, cx);
18965    });
18966    cx.executor().run_until_parked();
18967    let second_item_id = workspace
18968        .update(cx, |workspace, window, cx| {
18969            let active_item = workspace
18970                .active_item(cx)
18971                .expect("should have an active item after navigating into the 2nd buffer");
18972            let second_item_id = active_item.item_id();
18973            assert_ne!(
18974                second_item_id, multibuffer_item_id,
18975                "Should navigate away from the multibuffer"
18976            );
18977            assert_ne!(
18978                second_item_id, first_item_id,
18979                "Should navigate into the 2nd buffer and activate it"
18980            );
18981            assert_eq!(
18982                active_item.buffer_kind(cx),
18983                ItemBufferKind::Singleton,
18984                "New active item should be a singleton buffer"
18985            );
18986            assert_eq!(
18987                active_item
18988                    .act_as::<Editor>(cx)
18989                    .expect("should have navigated into an editor")
18990                    .read(cx)
18991                    .text(cx),
18992                sample_text_2
18993            );
18994
18995            workspace
18996                .go_back(workspace.active_pane().downgrade(), window, cx)
18997                .detach_and_log_err(cx);
18998
18999            second_item_id
19000        })
19001        .unwrap();
19002    cx.executor().run_until_parked();
19003    workspace
19004        .update(cx, |workspace, _, cx| {
19005            let active_item = workspace
19006                .active_item(cx)
19007                .expect("should have an active item after navigating back from the 2nd buffer");
19008            assert_eq!(
19009                active_item.item_id(),
19010                multibuffer_item_id,
19011                "Should navigate back from the 2nd buffer to the multi buffer"
19012            );
19013            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19014        })
19015        .unwrap();
19016
19017    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19018        editor.change_selections(
19019            SelectionEffects::scroll(Autoscroll::Next),
19020            window,
19021            cx,
19022            |s| s.select_ranges(Some(70..70)),
19023        );
19024        editor.open_excerpts(&OpenExcerpts, window, cx);
19025    });
19026    cx.executor().run_until_parked();
19027    workspace
19028        .update(cx, |workspace, window, cx| {
19029            let active_item = workspace
19030                .active_item(cx)
19031                .expect("should have an active item after navigating into the 3rd buffer");
19032            let third_item_id = active_item.item_id();
19033            assert_ne!(
19034                third_item_id, multibuffer_item_id,
19035                "Should navigate into the 3rd buffer and activate it"
19036            );
19037            assert_ne!(third_item_id, first_item_id);
19038            assert_ne!(third_item_id, second_item_id);
19039            assert_eq!(
19040                active_item.buffer_kind(cx),
19041                ItemBufferKind::Singleton,
19042                "New active item should be a singleton buffer"
19043            );
19044            assert_eq!(
19045                active_item
19046                    .act_as::<Editor>(cx)
19047                    .expect("should have navigated into an editor")
19048                    .read(cx)
19049                    .text(cx),
19050                sample_text_3
19051            );
19052
19053            workspace
19054                .go_back(workspace.active_pane().downgrade(), window, cx)
19055                .detach_and_log_err(cx);
19056        })
19057        .unwrap();
19058    cx.executor().run_until_parked();
19059    workspace
19060        .update(cx, |workspace, _, cx| {
19061            let active_item = workspace
19062                .active_item(cx)
19063                .expect("should have an active item after navigating back from the 3rd buffer");
19064            assert_eq!(
19065                active_item.item_id(),
19066                multibuffer_item_id,
19067                "Should navigate back from the 3rd buffer to the multi buffer"
19068            );
19069            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19070        })
19071        .unwrap();
19072}
19073
19074#[gpui::test]
19075async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19076    init_test(cx, |_| {});
19077
19078    let mut cx = EditorTestContext::new(cx).await;
19079
19080    let diff_base = r#"
19081        use some::mod;
19082
19083        const A: u32 = 42;
19084
19085        fn main() {
19086            println!("hello");
19087
19088            println!("world");
19089        }
19090        "#
19091    .unindent();
19092
19093    cx.set_state(
19094        &r#"
19095        use some::modified;
19096
19097        ˇ
19098        fn main() {
19099            println!("hello there");
19100
19101            println!("around the");
19102            println!("world");
19103        }
19104        "#
19105        .unindent(),
19106    );
19107
19108    cx.set_head_text(&diff_base);
19109    executor.run_until_parked();
19110
19111    cx.update_editor(|editor, window, cx| {
19112        editor.go_to_next_hunk(&GoToHunk, window, cx);
19113        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19114    });
19115    executor.run_until_parked();
19116    cx.assert_state_with_diff(
19117        r#"
19118          use some::modified;
19119
19120
19121          fn main() {
19122        -     println!("hello");
19123        + ˇ    println!("hello there");
19124
19125              println!("around the");
19126              println!("world");
19127          }
19128        "#
19129        .unindent(),
19130    );
19131
19132    cx.update_editor(|editor, window, cx| {
19133        for _ in 0..2 {
19134            editor.go_to_next_hunk(&GoToHunk, window, cx);
19135            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19136        }
19137    });
19138    executor.run_until_parked();
19139    cx.assert_state_with_diff(
19140        r#"
19141        - use some::mod;
19142        + ˇuse some::modified;
19143
19144
19145          fn main() {
19146        -     println!("hello");
19147        +     println!("hello there");
19148
19149        +     println!("around the");
19150              println!("world");
19151          }
19152        "#
19153        .unindent(),
19154    );
19155
19156    cx.update_editor(|editor, window, cx| {
19157        editor.go_to_next_hunk(&GoToHunk, window, cx);
19158        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19159    });
19160    executor.run_until_parked();
19161    cx.assert_state_with_diff(
19162        r#"
19163        - use some::mod;
19164        + use some::modified;
19165
19166        - const A: u32 = 42;
19167          ˇ
19168          fn main() {
19169        -     println!("hello");
19170        +     println!("hello there");
19171
19172        +     println!("around the");
19173              println!("world");
19174          }
19175        "#
19176        .unindent(),
19177    );
19178
19179    cx.update_editor(|editor, window, cx| {
19180        editor.cancel(&Cancel, window, cx);
19181    });
19182
19183    cx.assert_state_with_diff(
19184        r#"
19185          use some::modified;
19186
19187          ˇ
19188          fn main() {
19189              println!("hello there");
19190
19191              println!("around the");
19192              println!("world");
19193          }
19194        "#
19195        .unindent(),
19196    );
19197}
19198
19199#[gpui::test]
19200async fn test_diff_base_change_with_expanded_diff_hunks(
19201    executor: BackgroundExecutor,
19202    cx: &mut TestAppContext,
19203) {
19204    init_test(cx, |_| {});
19205
19206    let mut cx = EditorTestContext::new(cx).await;
19207
19208    let diff_base = r#"
19209        use some::mod1;
19210        use some::mod2;
19211
19212        const A: u32 = 42;
19213        const B: u32 = 42;
19214        const C: u32 = 42;
19215
19216        fn main() {
19217            println!("hello");
19218
19219            println!("world");
19220        }
19221        "#
19222    .unindent();
19223
19224    cx.set_state(
19225        &r#"
19226        use some::mod2;
19227
19228        const A: u32 = 42;
19229        const C: u32 = 42;
19230
19231        fn main(ˇ) {
19232            //println!("hello");
19233
19234            println!("world");
19235            //
19236            //
19237        }
19238        "#
19239        .unindent(),
19240    );
19241
19242    cx.set_head_text(&diff_base);
19243    executor.run_until_parked();
19244
19245    cx.update_editor(|editor, window, cx| {
19246        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19247    });
19248    executor.run_until_parked();
19249    cx.assert_state_with_diff(
19250        r#"
19251        - use some::mod1;
19252          use some::mod2;
19253
19254          const A: u32 = 42;
19255        - const B: u32 = 42;
19256          const C: u32 = 42;
19257
19258          fn main(ˇ) {
19259        -     println!("hello");
19260        +     //println!("hello");
19261
19262              println!("world");
19263        +     //
19264        +     //
19265          }
19266        "#
19267        .unindent(),
19268    );
19269
19270    cx.set_head_text("new diff base!");
19271    executor.run_until_parked();
19272    cx.assert_state_with_diff(
19273        r#"
19274        - new diff base!
19275        + use some::mod2;
19276        +
19277        + const A: u32 = 42;
19278        + const C: u32 = 42;
19279        +
19280        + fn main(ˇ) {
19281        +     //println!("hello");
19282        +
19283        +     println!("world");
19284        +     //
19285        +     //
19286        + }
19287        "#
19288        .unindent(),
19289    );
19290}
19291
19292#[gpui::test]
19293async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19294    init_test(cx, |_| {});
19295
19296    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19297    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19298    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19299    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19300    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19301    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19302
19303    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19304    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19305    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19306
19307    let multi_buffer = cx.new(|cx| {
19308        let mut multibuffer = MultiBuffer::new(ReadWrite);
19309        multibuffer.push_excerpts(
19310            buffer_1.clone(),
19311            [
19312                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19313                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19314                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19315            ],
19316            cx,
19317        );
19318        multibuffer.push_excerpts(
19319            buffer_2.clone(),
19320            [
19321                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19322                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19323                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19324            ],
19325            cx,
19326        );
19327        multibuffer.push_excerpts(
19328            buffer_3.clone(),
19329            [
19330                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19331                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19332                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19333            ],
19334            cx,
19335        );
19336        multibuffer
19337    });
19338
19339    let editor =
19340        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19341    editor
19342        .update(cx, |editor, _window, cx| {
19343            for (buffer, diff_base) in [
19344                (buffer_1.clone(), file_1_old),
19345                (buffer_2.clone(), file_2_old),
19346                (buffer_3.clone(), file_3_old),
19347            ] {
19348                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19349                editor
19350                    .buffer
19351                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19352            }
19353        })
19354        .unwrap();
19355
19356    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19357    cx.run_until_parked();
19358
19359    cx.assert_editor_state(
19360        &"
19361            ˇaaa
19362            ccc
19363            ddd
19364
19365            ggg
19366            hhh
19367
19368
19369            lll
19370            mmm
19371            NNN
19372
19373            qqq
19374            rrr
19375
19376            uuu
19377            111
19378            222
19379            333
19380
19381            666
19382            777
19383
19384            000
19385            !!!"
19386        .unindent(),
19387    );
19388
19389    cx.update_editor(|editor, window, cx| {
19390        editor.select_all(&SelectAll, window, cx);
19391        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19392    });
19393    cx.executor().run_until_parked();
19394
19395    cx.assert_state_with_diff(
19396        "
19397            «aaa
19398          - bbb
19399            ccc
19400            ddd
19401
19402            ggg
19403            hhh
19404
19405
19406            lll
19407            mmm
19408          - nnn
19409          + NNN
19410
19411            qqq
19412            rrr
19413
19414            uuu
19415            111
19416            222
19417            333
19418
19419          + 666
19420            777
19421
19422            000
19423            !!!ˇ»"
19424            .unindent(),
19425    );
19426}
19427
19428#[gpui::test]
19429async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19430    init_test(cx, |_| {});
19431
19432    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19433    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19434
19435    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19436    let multi_buffer = cx.new(|cx| {
19437        let mut multibuffer = MultiBuffer::new(ReadWrite);
19438        multibuffer.push_excerpts(
19439            buffer.clone(),
19440            [
19441                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19442                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19443                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19444            ],
19445            cx,
19446        );
19447        multibuffer
19448    });
19449
19450    let editor =
19451        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19452    editor
19453        .update(cx, |editor, _window, cx| {
19454            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19455            editor
19456                .buffer
19457                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19458        })
19459        .unwrap();
19460
19461    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19462    cx.run_until_parked();
19463
19464    cx.update_editor(|editor, window, cx| {
19465        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19466    });
19467    cx.executor().run_until_parked();
19468
19469    // When the start of a hunk coincides with the start of its excerpt,
19470    // the hunk is expanded. When the start of a hunk is earlier than
19471    // the start of its excerpt, the hunk is not expanded.
19472    cx.assert_state_with_diff(
19473        "
19474            ˇaaa
19475          - bbb
19476          + BBB
19477
19478          - ddd
19479          - eee
19480          + DDD
19481          + EEE
19482            fff
19483
19484            iii
19485        "
19486        .unindent(),
19487    );
19488}
19489
19490#[gpui::test]
19491async fn test_edits_around_expanded_insertion_hunks(
19492    executor: BackgroundExecutor,
19493    cx: &mut TestAppContext,
19494) {
19495    init_test(cx, |_| {});
19496
19497    let mut cx = EditorTestContext::new(cx).await;
19498
19499    let diff_base = r#"
19500        use some::mod1;
19501        use some::mod2;
19502
19503        const A: u32 = 42;
19504
19505        fn main() {
19506            println!("hello");
19507
19508            println!("world");
19509        }
19510        "#
19511    .unindent();
19512    executor.run_until_parked();
19513    cx.set_state(
19514        &r#"
19515        use some::mod1;
19516        use some::mod2;
19517
19518        const A: u32 = 42;
19519        const B: u32 = 42;
19520        const C: u32 = 42;
19521        ˇ
19522
19523        fn main() {
19524            println!("hello");
19525
19526            println!("world");
19527        }
19528        "#
19529        .unindent(),
19530    );
19531
19532    cx.set_head_text(&diff_base);
19533    executor.run_until_parked();
19534
19535    cx.update_editor(|editor, window, cx| {
19536        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19537    });
19538    executor.run_until_parked();
19539
19540    cx.assert_state_with_diff(
19541        r#"
19542        use some::mod1;
19543        use some::mod2;
19544
19545        const A: u32 = 42;
19546      + const B: u32 = 42;
19547      + const C: u32 = 42;
19548      + ˇ
19549
19550        fn main() {
19551            println!("hello");
19552
19553            println!("world");
19554        }
19555      "#
19556        .unindent(),
19557    );
19558
19559    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19560    executor.run_until_parked();
19561
19562    cx.assert_state_with_diff(
19563        r#"
19564        use some::mod1;
19565        use some::mod2;
19566
19567        const A: u32 = 42;
19568      + const B: u32 = 42;
19569      + const C: u32 = 42;
19570      + const D: u32 = 42;
19571      + ˇ
19572
19573        fn main() {
19574            println!("hello");
19575
19576            println!("world");
19577        }
19578      "#
19579        .unindent(),
19580    );
19581
19582    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19583    executor.run_until_parked();
19584
19585    cx.assert_state_with_diff(
19586        r#"
19587        use some::mod1;
19588        use some::mod2;
19589
19590        const A: u32 = 42;
19591      + const B: u32 = 42;
19592      + const C: u32 = 42;
19593      + const D: u32 = 42;
19594      + const E: u32 = 42;
19595      + ˇ
19596
19597        fn main() {
19598            println!("hello");
19599
19600            println!("world");
19601        }
19602      "#
19603        .unindent(),
19604    );
19605
19606    cx.update_editor(|editor, window, cx| {
19607        editor.delete_line(&DeleteLine, window, cx);
19608    });
19609    executor.run_until_parked();
19610
19611    cx.assert_state_with_diff(
19612        r#"
19613        use some::mod1;
19614        use some::mod2;
19615
19616        const A: u32 = 42;
19617      + const B: u32 = 42;
19618      + const C: u32 = 42;
19619      + const D: u32 = 42;
19620      + const E: u32 = 42;
19621        ˇ
19622        fn main() {
19623            println!("hello");
19624
19625            println!("world");
19626        }
19627      "#
19628        .unindent(),
19629    );
19630
19631    cx.update_editor(|editor, window, cx| {
19632        editor.move_up(&MoveUp, window, cx);
19633        editor.delete_line(&DeleteLine, window, cx);
19634        editor.move_up(&MoveUp, window, cx);
19635        editor.delete_line(&DeleteLine, window, cx);
19636        editor.move_up(&MoveUp, window, cx);
19637        editor.delete_line(&DeleteLine, window, cx);
19638    });
19639    executor.run_until_parked();
19640    cx.assert_state_with_diff(
19641        r#"
19642        use some::mod1;
19643        use some::mod2;
19644
19645        const A: u32 = 42;
19646      + const B: u32 = 42;
19647        ˇ
19648        fn main() {
19649            println!("hello");
19650
19651            println!("world");
19652        }
19653      "#
19654        .unindent(),
19655    );
19656
19657    cx.update_editor(|editor, window, cx| {
19658        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19659        editor.delete_line(&DeleteLine, window, cx);
19660    });
19661    executor.run_until_parked();
19662    cx.assert_state_with_diff(
19663        r#"
19664        ˇ
19665        fn main() {
19666            println!("hello");
19667
19668            println!("world");
19669        }
19670      "#
19671        .unindent(),
19672    );
19673}
19674
19675#[gpui::test]
19676async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19677    init_test(cx, |_| {});
19678
19679    let mut cx = EditorTestContext::new(cx).await;
19680    cx.set_head_text(indoc! { "
19681        one
19682        two
19683        three
19684        four
19685        five
19686        "
19687    });
19688    cx.set_state(indoc! { "
19689        one
19690        ˇthree
19691        five
19692    "});
19693    cx.run_until_parked();
19694    cx.update_editor(|editor, window, cx| {
19695        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19696    });
19697    cx.assert_state_with_diff(
19698        indoc! { "
19699        one
19700      - two
19701        ˇthree
19702      - four
19703        five
19704    "}
19705        .to_string(),
19706    );
19707    cx.update_editor(|editor, window, cx| {
19708        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19709    });
19710
19711    cx.assert_state_with_diff(
19712        indoc! { "
19713        one
19714        ˇthree
19715        five
19716    "}
19717        .to_string(),
19718    );
19719
19720    cx.set_state(indoc! { "
19721        one
19722        ˇTWO
19723        three
19724        four
19725        five
19726    "});
19727    cx.run_until_parked();
19728    cx.update_editor(|editor, window, cx| {
19729        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19730    });
19731
19732    cx.assert_state_with_diff(
19733        indoc! { "
19734            one
19735          - two
19736          + ˇTWO
19737            three
19738            four
19739            five
19740        "}
19741        .to_string(),
19742    );
19743    cx.update_editor(|editor, window, cx| {
19744        editor.move_up(&Default::default(), window, cx);
19745        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19746    });
19747    cx.assert_state_with_diff(
19748        indoc! { "
19749            one
19750            ˇTWO
19751            three
19752            four
19753            five
19754        "}
19755        .to_string(),
19756    );
19757}
19758
19759#[gpui::test]
19760async fn test_edits_around_expanded_deletion_hunks(
19761    executor: BackgroundExecutor,
19762    cx: &mut TestAppContext,
19763) {
19764    init_test(cx, |_| {});
19765
19766    let mut cx = EditorTestContext::new(cx).await;
19767
19768    let diff_base = r#"
19769        use some::mod1;
19770        use some::mod2;
19771
19772        const A: u32 = 42;
19773        const B: u32 = 42;
19774        const C: u32 = 42;
19775
19776
19777        fn main() {
19778            println!("hello");
19779
19780            println!("world");
19781        }
19782    "#
19783    .unindent();
19784    executor.run_until_parked();
19785    cx.set_state(
19786        &r#"
19787        use some::mod1;
19788        use some::mod2;
19789
19790        ˇconst B: u32 = 42;
19791        const C: u32 = 42;
19792
19793
19794        fn main() {
19795            println!("hello");
19796
19797            println!("world");
19798        }
19799        "#
19800        .unindent(),
19801    );
19802
19803    cx.set_head_text(&diff_base);
19804    executor.run_until_parked();
19805
19806    cx.update_editor(|editor, window, cx| {
19807        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19808    });
19809    executor.run_until_parked();
19810
19811    cx.assert_state_with_diff(
19812        r#"
19813        use some::mod1;
19814        use some::mod2;
19815
19816      - const A: u32 = 42;
19817        ˇconst B: u32 = 42;
19818        const C: u32 = 42;
19819
19820
19821        fn main() {
19822            println!("hello");
19823
19824            println!("world");
19825        }
19826      "#
19827        .unindent(),
19828    );
19829
19830    cx.update_editor(|editor, window, cx| {
19831        editor.delete_line(&DeleteLine, window, cx);
19832    });
19833    executor.run_until_parked();
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
19843
19844        fn main() {
19845            println!("hello");
19846
19847            println!("world");
19848        }
19849      "#
19850        .unindent(),
19851    );
19852
19853    cx.update_editor(|editor, window, cx| {
19854        editor.delete_line(&DeleteLine, window, cx);
19855    });
19856    executor.run_until_parked();
19857    cx.assert_state_with_diff(
19858        r#"
19859        use some::mod1;
19860        use some::mod2;
19861
19862      - const A: u32 = 42;
19863      - const B: u32 = 42;
19864      - const C: u32 = 42;
19865        ˇ
19866
19867        fn main() {
19868            println!("hello");
19869
19870            println!("world");
19871        }
19872      "#
19873        .unindent(),
19874    );
19875
19876    cx.update_editor(|editor, window, cx| {
19877        editor.handle_input("replacement", window, cx);
19878    });
19879    executor.run_until_parked();
19880    cx.assert_state_with_diff(
19881        r#"
19882        use some::mod1;
19883        use some::mod2;
19884
19885      - const A: u32 = 42;
19886      - const B: u32 = 42;
19887      - const C: u32 = 42;
19888      -
19889      + replacementˇ
19890
19891        fn main() {
19892            println!("hello");
19893
19894            println!("world");
19895        }
19896      "#
19897        .unindent(),
19898    );
19899}
19900
19901#[gpui::test]
19902async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19903    init_test(cx, |_| {});
19904
19905    let mut cx = EditorTestContext::new(cx).await;
19906
19907    let base_text = r#"
19908        one
19909        two
19910        three
19911        four
19912        five
19913    "#
19914    .unindent();
19915    executor.run_until_parked();
19916    cx.set_state(
19917        &r#"
19918        one
19919        two
19920        fˇour
19921        five
19922        "#
19923        .unindent(),
19924    );
19925
19926    cx.set_head_text(&base_text);
19927    executor.run_until_parked();
19928
19929    cx.update_editor(|editor, window, cx| {
19930        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19931    });
19932    executor.run_until_parked();
19933
19934    cx.assert_state_with_diff(
19935        r#"
19936          one
19937          two
19938        - three
19939          fˇour
19940          five
19941        "#
19942        .unindent(),
19943    );
19944
19945    cx.update_editor(|editor, window, cx| {
19946        editor.backspace(&Backspace, window, cx);
19947        editor.backspace(&Backspace, window, cx);
19948    });
19949    executor.run_until_parked();
19950    cx.assert_state_with_diff(
19951        r#"
19952          one
19953          two
19954        - threeˇ
19955        - four
19956        + our
19957          five
19958        "#
19959        .unindent(),
19960    );
19961}
19962
19963#[gpui::test]
19964async fn test_edit_after_expanded_modification_hunk(
19965    executor: BackgroundExecutor,
19966    cx: &mut TestAppContext,
19967) {
19968    init_test(cx, |_| {});
19969
19970    let mut cx = EditorTestContext::new(cx).await;
19971
19972    let diff_base = r#"
19973        use some::mod1;
19974        use some::mod2;
19975
19976        const A: u32 = 42;
19977        const B: u32 = 42;
19978        const C: u32 = 42;
19979        const D: u32 = 42;
19980
19981
19982        fn main() {
19983            println!("hello");
19984
19985            println!("world");
19986        }"#
19987    .unindent();
19988
19989    cx.set_state(
19990        &r#"
19991        use some::mod1;
19992        use some::mod2;
19993
19994        const A: u32 = 42;
19995        const B: u32 = 42;
19996        const C: u32 = 43ˇ
19997        const D: u32 = 42;
19998
19999
20000        fn main() {
20001            println!("hello");
20002
20003            println!("world");
20004        }"#
20005        .unindent(),
20006    );
20007
20008    cx.set_head_text(&diff_base);
20009    executor.run_until_parked();
20010    cx.update_editor(|editor, window, cx| {
20011        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20012    });
20013    executor.run_until_parked();
20014
20015    cx.assert_state_with_diff(
20016        r#"
20017        use some::mod1;
20018        use some::mod2;
20019
20020        const A: u32 = 42;
20021        const B: u32 = 42;
20022      - const C: u32 = 42;
20023      + const C: u32 = 43ˇ
20024        const D: u32 = 42;
20025
20026
20027        fn main() {
20028            println!("hello");
20029
20030            println!("world");
20031        }"#
20032        .unindent(),
20033    );
20034
20035    cx.update_editor(|editor, window, cx| {
20036        editor.handle_input("\nnew_line\n", window, cx);
20037    });
20038    executor.run_until_parked();
20039
20040    cx.assert_state_with_diff(
20041        r#"
20042        use some::mod1;
20043        use some::mod2;
20044
20045        const A: u32 = 42;
20046        const B: u32 = 42;
20047      - const C: u32 = 42;
20048      + const C: u32 = 43
20049      + new_line
20050      + ˇ
20051        const D: u32 = 42;
20052
20053
20054        fn main() {
20055            println!("hello");
20056
20057            println!("world");
20058        }"#
20059        .unindent(),
20060    );
20061}
20062
20063#[gpui::test]
20064async fn test_stage_and_unstage_added_file_hunk(
20065    executor: BackgroundExecutor,
20066    cx: &mut TestAppContext,
20067) {
20068    init_test(cx, |_| {});
20069
20070    let mut cx = EditorTestContext::new(cx).await;
20071    cx.update_editor(|editor, _, cx| {
20072        editor.set_expand_all_diff_hunks(cx);
20073    });
20074
20075    let working_copy = r#"
20076            ˇfn main() {
20077                println!("hello, world!");
20078            }
20079        "#
20080    .unindent();
20081
20082    cx.set_state(&working_copy);
20083    executor.run_until_parked();
20084
20085    cx.assert_state_with_diff(
20086        r#"
20087            + ˇfn main() {
20088            +     println!("hello, world!");
20089            + }
20090        "#
20091        .unindent(),
20092    );
20093    cx.assert_index_text(None);
20094
20095    cx.update_editor(|editor, window, cx| {
20096        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20097    });
20098    executor.run_until_parked();
20099    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20100    cx.assert_state_with_diff(
20101        r#"
20102            + ˇfn main() {
20103            +     println!("hello, world!");
20104            + }
20105        "#
20106        .unindent(),
20107    );
20108
20109    cx.update_editor(|editor, window, cx| {
20110        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20111    });
20112    executor.run_until_parked();
20113    cx.assert_index_text(None);
20114}
20115
20116async fn setup_indent_guides_editor(
20117    text: &str,
20118    cx: &mut TestAppContext,
20119) -> (BufferId, EditorTestContext) {
20120    init_test(cx, |_| {});
20121
20122    let mut cx = EditorTestContext::new(cx).await;
20123
20124    let buffer_id = cx.update_editor(|editor, window, cx| {
20125        editor.set_text(text, window, cx);
20126        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20127
20128        buffer_ids[0]
20129    });
20130
20131    (buffer_id, cx)
20132}
20133
20134fn assert_indent_guides(
20135    range: Range<u32>,
20136    expected: Vec<IndentGuide>,
20137    active_indices: Option<Vec<usize>>,
20138    cx: &mut EditorTestContext,
20139) {
20140    let indent_guides = cx.update_editor(|editor, window, cx| {
20141        let snapshot = editor.snapshot(window, cx).display_snapshot;
20142        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20143            editor,
20144            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20145            true,
20146            &snapshot,
20147            cx,
20148        );
20149
20150        indent_guides.sort_by(|a, b| {
20151            a.depth.cmp(&b.depth).then(
20152                a.start_row
20153                    .cmp(&b.start_row)
20154                    .then(a.end_row.cmp(&b.end_row)),
20155            )
20156        });
20157        indent_guides
20158    });
20159
20160    if let Some(expected) = active_indices {
20161        let active_indices = cx.update_editor(|editor, window, cx| {
20162            let snapshot = editor.snapshot(window, cx).display_snapshot;
20163            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20164        });
20165
20166        assert_eq!(
20167            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20168            expected,
20169            "Active indent guide indices do not match"
20170        );
20171    }
20172
20173    assert_eq!(indent_guides, expected, "Indent guides do not match");
20174}
20175
20176fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20177    IndentGuide {
20178        buffer_id,
20179        start_row: MultiBufferRow(start_row),
20180        end_row: MultiBufferRow(end_row),
20181        depth,
20182        tab_size: 4,
20183        settings: IndentGuideSettings {
20184            enabled: true,
20185            line_width: 1,
20186            active_line_width: 1,
20187            coloring: IndentGuideColoring::default(),
20188            background_coloring: IndentGuideBackgroundColoring::default(),
20189        },
20190    }
20191}
20192
20193#[gpui::test]
20194async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20195    let (buffer_id, mut cx) = setup_indent_guides_editor(
20196        &"
20197        fn main() {
20198            let a = 1;
20199        }"
20200        .unindent(),
20201        cx,
20202    )
20203    .await;
20204
20205    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20206}
20207
20208#[gpui::test]
20209async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20210    let (buffer_id, mut cx) = setup_indent_guides_editor(
20211        &"
20212        fn main() {
20213            let a = 1;
20214            let b = 2;
20215        }"
20216        .unindent(),
20217        cx,
20218    )
20219    .await;
20220
20221    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20222}
20223
20224#[gpui::test]
20225async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20226    let (buffer_id, mut cx) = setup_indent_guides_editor(
20227        &"
20228        fn main() {
20229            let a = 1;
20230            if a == 3 {
20231                let b = 2;
20232            } else {
20233                let c = 3;
20234            }
20235        }"
20236        .unindent(),
20237        cx,
20238    )
20239    .await;
20240
20241    assert_indent_guides(
20242        0..8,
20243        vec![
20244            indent_guide(buffer_id, 1, 6, 0),
20245            indent_guide(buffer_id, 3, 3, 1),
20246            indent_guide(buffer_id, 5, 5, 1),
20247        ],
20248        None,
20249        &mut cx,
20250    );
20251}
20252
20253#[gpui::test]
20254async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20255    let (buffer_id, mut cx) = setup_indent_guides_editor(
20256        &"
20257        fn main() {
20258            let a = 1;
20259                let b = 2;
20260            let c = 3;
20261        }"
20262        .unindent(),
20263        cx,
20264    )
20265    .await;
20266
20267    assert_indent_guides(
20268        0..5,
20269        vec![
20270            indent_guide(buffer_id, 1, 3, 0),
20271            indent_guide(buffer_id, 2, 2, 1),
20272        ],
20273        None,
20274        &mut cx,
20275    );
20276}
20277
20278#[gpui::test]
20279async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20280    let (buffer_id, mut cx) = setup_indent_guides_editor(
20281        &"
20282        fn main() {
20283            let a = 1;
20284
20285            let c = 3;
20286        }"
20287        .unindent(),
20288        cx,
20289    )
20290    .await;
20291
20292    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20293}
20294
20295#[gpui::test]
20296async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20297    let (buffer_id, mut cx) = setup_indent_guides_editor(
20298        &"
20299        fn main() {
20300            let a = 1;
20301
20302            let c = 3;
20303
20304            if a == 3 {
20305                let b = 2;
20306            } else {
20307                let c = 3;
20308            }
20309        }"
20310        .unindent(),
20311        cx,
20312    )
20313    .await;
20314
20315    assert_indent_guides(
20316        0..11,
20317        vec![
20318            indent_guide(buffer_id, 1, 9, 0),
20319            indent_guide(buffer_id, 6, 6, 1),
20320            indent_guide(buffer_id, 8, 8, 1),
20321        ],
20322        None,
20323        &mut cx,
20324    );
20325}
20326
20327#[gpui::test]
20328async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20329    let (buffer_id, mut cx) = setup_indent_guides_editor(
20330        &"
20331        fn main() {
20332            let a = 1;
20333
20334            let c = 3;
20335
20336            if a == 3 {
20337                let b = 2;
20338            } else {
20339                let c = 3;
20340            }
20341        }"
20342        .unindent(),
20343        cx,
20344    )
20345    .await;
20346
20347    assert_indent_guides(
20348        1..11,
20349        vec![
20350            indent_guide(buffer_id, 1, 9, 0),
20351            indent_guide(buffer_id, 6, 6, 1),
20352            indent_guide(buffer_id, 8, 8, 1),
20353        ],
20354        None,
20355        &mut cx,
20356    );
20357}
20358
20359#[gpui::test]
20360async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20361    let (buffer_id, mut cx) = setup_indent_guides_editor(
20362        &"
20363        fn main() {
20364            let a = 1;
20365
20366            let c = 3;
20367
20368            if a == 3 {
20369                let b = 2;
20370            } else {
20371                let c = 3;
20372            }
20373        }"
20374        .unindent(),
20375        cx,
20376    )
20377    .await;
20378
20379    assert_indent_guides(
20380        1..10,
20381        vec![
20382            indent_guide(buffer_id, 1, 9, 0),
20383            indent_guide(buffer_id, 6, 6, 1),
20384            indent_guide(buffer_id, 8, 8, 1),
20385        ],
20386        None,
20387        &mut cx,
20388    );
20389}
20390
20391#[gpui::test]
20392async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20393    let (buffer_id, mut cx) = setup_indent_guides_editor(
20394        &"
20395        fn main() {
20396            if a {
20397                b(
20398                    c,
20399                    d,
20400                )
20401            } else {
20402                e(
20403                    f
20404                )
20405            }
20406        }"
20407        .unindent(),
20408        cx,
20409    )
20410    .await;
20411
20412    assert_indent_guides(
20413        0..11,
20414        vec![
20415            indent_guide(buffer_id, 1, 10, 0),
20416            indent_guide(buffer_id, 2, 5, 1),
20417            indent_guide(buffer_id, 7, 9, 1),
20418            indent_guide(buffer_id, 3, 4, 2),
20419            indent_guide(buffer_id, 8, 8, 2),
20420        ],
20421        None,
20422        &mut cx,
20423    );
20424
20425    cx.update_editor(|editor, window, cx| {
20426        editor.fold_at(MultiBufferRow(2), window, cx);
20427        assert_eq!(
20428            editor.display_text(cx),
20429            "
20430            fn main() {
20431                if a {
20432                    b(⋯
20433                    )
20434                } else {
20435                    e(
20436                        f
20437                    )
20438                }
20439            }"
20440            .unindent()
20441        );
20442    });
20443
20444    assert_indent_guides(
20445        0..11,
20446        vec![
20447            indent_guide(buffer_id, 1, 10, 0),
20448            indent_guide(buffer_id, 2, 5, 1),
20449            indent_guide(buffer_id, 7, 9, 1),
20450            indent_guide(buffer_id, 8, 8, 2),
20451        ],
20452        None,
20453        &mut cx,
20454    );
20455}
20456
20457#[gpui::test]
20458async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20459    let (buffer_id, mut cx) = setup_indent_guides_editor(
20460        &"
20461        block1
20462            block2
20463                block3
20464                    block4
20465            block2
20466        block1
20467        block1"
20468            .unindent(),
20469        cx,
20470    )
20471    .await;
20472
20473    assert_indent_guides(
20474        1..10,
20475        vec![
20476            indent_guide(buffer_id, 1, 4, 0),
20477            indent_guide(buffer_id, 2, 3, 1),
20478            indent_guide(buffer_id, 3, 3, 2),
20479        ],
20480        None,
20481        &mut cx,
20482    );
20483}
20484
20485#[gpui::test]
20486async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20487    let (buffer_id, mut cx) = setup_indent_guides_editor(
20488        &"
20489        block1
20490            block2
20491                block3
20492
20493        block1
20494        block1"
20495            .unindent(),
20496        cx,
20497    )
20498    .await;
20499
20500    assert_indent_guides(
20501        0..6,
20502        vec![
20503            indent_guide(buffer_id, 1, 2, 0),
20504            indent_guide(buffer_id, 2, 2, 1),
20505        ],
20506        None,
20507        &mut cx,
20508    );
20509}
20510
20511#[gpui::test]
20512async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20513    let (buffer_id, mut cx) = setup_indent_guides_editor(
20514        &"
20515        function component() {
20516        \treturn (
20517        \t\t\t
20518        \t\t<div>
20519        \t\t\t<abc></abc>
20520        \t\t</div>
20521        \t)
20522        }"
20523        .unindent(),
20524        cx,
20525    )
20526    .await;
20527
20528    assert_indent_guides(
20529        0..8,
20530        vec![
20531            indent_guide(buffer_id, 1, 6, 0),
20532            indent_guide(buffer_id, 2, 5, 1),
20533            indent_guide(buffer_id, 4, 4, 2),
20534        ],
20535        None,
20536        &mut cx,
20537    );
20538}
20539
20540#[gpui::test]
20541async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20542    let (buffer_id, mut cx) = setup_indent_guides_editor(
20543        &"
20544        function component() {
20545        \treturn (
20546        \t
20547        \t\t<div>
20548        \t\t\t<abc></abc>
20549        \t\t</div>
20550        \t)
20551        }"
20552        .unindent(),
20553        cx,
20554    )
20555    .await;
20556
20557    assert_indent_guides(
20558        0..8,
20559        vec![
20560            indent_guide(buffer_id, 1, 6, 0),
20561            indent_guide(buffer_id, 2, 5, 1),
20562            indent_guide(buffer_id, 4, 4, 2),
20563        ],
20564        None,
20565        &mut cx,
20566    );
20567}
20568
20569#[gpui::test]
20570async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20571    let (buffer_id, mut cx) = setup_indent_guides_editor(
20572        &"
20573        block1
20574
20575
20576
20577            block2
20578        "
20579        .unindent(),
20580        cx,
20581    )
20582    .await;
20583
20584    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20585}
20586
20587#[gpui::test]
20588async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20589    let (buffer_id, mut cx) = setup_indent_guides_editor(
20590        &"
20591        def a:
20592        \tb = 3
20593        \tif True:
20594        \t\tc = 4
20595        \t\td = 5
20596        \tprint(b)
20597        "
20598        .unindent(),
20599        cx,
20600    )
20601    .await;
20602
20603    assert_indent_guides(
20604        0..6,
20605        vec![
20606            indent_guide(buffer_id, 1, 5, 0),
20607            indent_guide(buffer_id, 3, 4, 1),
20608        ],
20609        None,
20610        &mut cx,
20611    );
20612}
20613
20614#[gpui::test]
20615async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20616    let (buffer_id, mut cx) = setup_indent_guides_editor(
20617        &"
20618    fn main() {
20619        let a = 1;
20620    }"
20621        .unindent(),
20622        cx,
20623    )
20624    .await;
20625
20626    cx.update_editor(|editor, window, cx| {
20627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20628            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20629        });
20630    });
20631
20632    assert_indent_guides(
20633        0..3,
20634        vec![indent_guide(buffer_id, 1, 1, 0)],
20635        Some(vec![0]),
20636        &mut cx,
20637    );
20638}
20639
20640#[gpui::test]
20641async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20642    let (buffer_id, mut cx) = setup_indent_guides_editor(
20643        &"
20644    fn main() {
20645        if 1 == 2 {
20646            let a = 1;
20647        }
20648    }"
20649        .unindent(),
20650        cx,
20651    )
20652    .await;
20653
20654    cx.update_editor(|editor, window, cx| {
20655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20656            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20657        });
20658    });
20659
20660    assert_indent_guides(
20661        0..4,
20662        vec![
20663            indent_guide(buffer_id, 1, 3, 0),
20664            indent_guide(buffer_id, 2, 2, 1),
20665        ],
20666        Some(vec![1]),
20667        &mut cx,
20668    );
20669
20670    cx.update_editor(|editor, window, cx| {
20671        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20672            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20673        });
20674    });
20675
20676    assert_indent_guides(
20677        0..4,
20678        vec![
20679            indent_guide(buffer_id, 1, 3, 0),
20680            indent_guide(buffer_id, 2, 2, 1),
20681        ],
20682        Some(vec![1]),
20683        &mut cx,
20684    );
20685
20686    cx.update_editor(|editor, window, cx| {
20687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20688            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20689        });
20690    });
20691
20692    assert_indent_guides(
20693        0..4,
20694        vec![
20695            indent_guide(buffer_id, 1, 3, 0),
20696            indent_guide(buffer_id, 2, 2, 1),
20697        ],
20698        Some(vec![0]),
20699        &mut cx,
20700    );
20701}
20702
20703#[gpui::test]
20704async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20705    let (buffer_id, mut cx) = setup_indent_guides_editor(
20706        &"
20707    fn main() {
20708        let a = 1;
20709
20710        let b = 2;
20711    }"
20712        .unindent(),
20713        cx,
20714    )
20715    .await;
20716
20717    cx.update_editor(|editor, window, cx| {
20718        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20719            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20720        });
20721    });
20722
20723    assert_indent_guides(
20724        0..5,
20725        vec![indent_guide(buffer_id, 1, 3, 0)],
20726        Some(vec![0]),
20727        &mut cx,
20728    );
20729}
20730
20731#[gpui::test]
20732async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20733    let (buffer_id, mut cx) = setup_indent_guides_editor(
20734        &"
20735    def m:
20736        a = 1
20737        pass"
20738            .unindent(),
20739        cx,
20740    )
20741    .await;
20742
20743    cx.update_editor(|editor, window, cx| {
20744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20745            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20746        });
20747    });
20748
20749    assert_indent_guides(
20750        0..3,
20751        vec![indent_guide(buffer_id, 1, 2, 0)],
20752        Some(vec![0]),
20753        &mut cx,
20754    );
20755}
20756
20757#[gpui::test]
20758async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20759    init_test(cx, |_| {});
20760    let mut cx = EditorTestContext::new(cx).await;
20761    let text = indoc! {
20762        "
20763        impl A {
20764            fn b() {
20765                0;
20766                3;
20767                5;
20768                6;
20769                7;
20770            }
20771        }
20772        "
20773    };
20774    let base_text = indoc! {
20775        "
20776        impl A {
20777            fn b() {
20778                0;
20779                1;
20780                2;
20781                3;
20782                4;
20783            }
20784            fn c() {
20785                5;
20786                6;
20787                7;
20788            }
20789        }
20790        "
20791    };
20792
20793    cx.update_editor(|editor, window, cx| {
20794        editor.set_text(text, window, cx);
20795
20796        editor.buffer().update(cx, |multibuffer, cx| {
20797            let buffer = multibuffer.as_singleton().unwrap();
20798            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20799
20800            multibuffer.set_all_diff_hunks_expanded(cx);
20801            multibuffer.add_diff(diff, cx);
20802
20803            buffer.read(cx).remote_id()
20804        })
20805    });
20806    cx.run_until_parked();
20807
20808    cx.assert_state_with_diff(
20809        indoc! { "
20810          impl A {
20811              fn b() {
20812                  0;
20813        -         1;
20814        -         2;
20815                  3;
20816        -         4;
20817        -     }
20818        -     fn c() {
20819                  5;
20820                  6;
20821                  7;
20822              }
20823          }
20824          ˇ"
20825        }
20826        .to_string(),
20827    );
20828
20829    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20830        editor
20831            .snapshot(window, cx)
20832            .buffer_snapshot()
20833            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20834            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20835            .collect::<Vec<_>>()
20836    });
20837    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20838    assert_eq!(
20839        actual_guides,
20840        vec![
20841            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20842            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20843            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20844        ]
20845    );
20846}
20847
20848#[gpui::test]
20849async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20850    init_test(cx, |_| {});
20851    let mut cx = EditorTestContext::new(cx).await;
20852
20853    let diff_base = r#"
20854        a
20855        b
20856        c
20857        "#
20858    .unindent();
20859
20860    cx.set_state(
20861        &r#"
20862        ˇA
20863        b
20864        C
20865        "#
20866        .unindent(),
20867    );
20868    cx.set_head_text(&diff_base);
20869    cx.update_editor(|editor, window, cx| {
20870        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20871    });
20872    executor.run_until_parked();
20873
20874    let both_hunks_expanded = r#"
20875        - a
20876        + ˇA
20877          b
20878        - c
20879        + C
20880        "#
20881    .unindent();
20882
20883    cx.assert_state_with_diff(both_hunks_expanded.clone());
20884
20885    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20886        let snapshot = editor.snapshot(window, cx);
20887        let hunks = editor
20888            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20889            .collect::<Vec<_>>();
20890        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20891        let buffer_id = hunks[0].buffer_id;
20892        hunks
20893            .into_iter()
20894            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20895            .collect::<Vec<_>>()
20896    });
20897    assert_eq!(hunk_ranges.len(), 2);
20898
20899    cx.update_editor(|editor, _, cx| {
20900        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20901    });
20902    executor.run_until_parked();
20903
20904    let second_hunk_expanded = r#"
20905          ˇA
20906          b
20907        - c
20908        + C
20909        "#
20910    .unindent();
20911
20912    cx.assert_state_with_diff(second_hunk_expanded);
20913
20914    cx.update_editor(|editor, _, cx| {
20915        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20916    });
20917    executor.run_until_parked();
20918
20919    cx.assert_state_with_diff(both_hunks_expanded.clone());
20920
20921    cx.update_editor(|editor, _, cx| {
20922        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20923    });
20924    executor.run_until_parked();
20925
20926    let first_hunk_expanded = r#"
20927        - a
20928        + ˇA
20929          b
20930          C
20931        "#
20932    .unindent();
20933
20934    cx.assert_state_with_diff(first_hunk_expanded);
20935
20936    cx.update_editor(|editor, _, cx| {
20937        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20938    });
20939    executor.run_until_parked();
20940
20941    cx.assert_state_with_diff(both_hunks_expanded);
20942
20943    cx.set_state(
20944        &r#"
20945        ˇA
20946        b
20947        "#
20948        .unindent(),
20949    );
20950    cx.run_until_parked();
20951
20952    // TODO this cursor position seems bad
20953    cx.assert_state_with_diff(
20954        r#"
20955        - ˇa
20956        + A
20957          b
20958        "#
20959        .unindent(),
20960    );
20961
20962    cx.update_editor(|editor, window, cx| {
20963        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20964    });
20965
20966    cx.assert_state_with_diff(
20967        r#"
20968            - ˇa
20969            + A
20970              b
20971            - c
20972            "#
20973        .unindent(),
20974    );
20975
20976    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20977        let snapshot = editor.snapshot(window, cx);
20978        let hunks = editor
20979            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20980            .collect::<Vec<_>>();
20981        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20982        let buffer_id = hunks[0].buffer_id;
20983        hunks
20984            .into_iter()
20985            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20986            .collect::<Vec<_>>()
20987    });
20988    assert_eq!(hunk_ranges.len(), 2);
20989
20990    cx.update_editor(|editor, _, cx| {
20991        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20992    });
20993    executor.run_until_parked();
20994
20995    cx.assert_state_with_diff(
20996        r#"
20997        - ˇa
20998        + A
20999          b
21000        "#
21001        .unindent(),
21002    );
21003}
21004
21005#[gpui::test]
21006async fn test_toggle_deletion_hunk_at_start_of_file(
21007    executor: BackgroundExecutor,
21008    cx: &mut TestAppContext,
21009) {
21010    init_test(cx, |_| {});
21011    let mut cx = EditorTestContext::new(cx).await;
21012
21013    let diff_base = r#"
21014        a
21015        b
21016        c
21017        "#
21018    .unindent();
21019
21020    cx.set_state(
21021        &r#"
21022        ˇb
21023        c
21024        "#
21025        .unindent(),
21026    );
21027    cx.set_head_text(&diff_base);
21028    cx.update_editor(|editor, window, cx| {
21029        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21030    });
21031    executor.run_until_parked();
21032
21033    let hunk_expanded = r#"
21034        - a
21035          ˇb
21036          c
21037        "#
21038    .unindent();
21039
21040    cx.assert_state_with_diff(hunk_expanded.clone());
21041
21042    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21043        let snapshot = editor.snapshot(window, cx);
21044        let hunks = editor
21045            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21046            .collect::<Vec<_>>();
21047        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21048        let buffer_id = hunks[0].buffer_id;
21049        hunks
21050            .into_iter()
21051            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21052            .collect::<Vec<_>>()
21053    });
21054    assert_eq!(hunk_ranges.len(), 1);
21055
21056    cx.update_editor(|editor, _, cx| {
21057        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21058    });
21059    executor.run_until_parked();
21060
21061    let hunk_collapsed = r#"
21062          ˇb
21063          c
21064        "#
21065    .unindent();
21066
21067    cx.assert_state_with_diff(hunk_collapsed);
21068
21069    cx.update_editor(|editor, _, cx| {
21070        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21071    });
21072    executor.run_until_parked();
21073
21074    cx.assert_state_with_diff(hunk_expanded);
21075}
21076
21077#[gpui::test]
21078async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21079    init_test(cx, |_| {});
21080
21081    let fs = FakeFs::new(cx.executor());
21082    fs.insert_tree(
21083        path!("/test"),
21084        json!({
21085            ".git": {},
21086            "file-1": "ONE\n",
21087            "file-2": "TWO\n",
21088            "file-3": "THREE\n",
21089        }),
21090    )
21091    .await;
21092
21093    fs.set_head_for_repo(
21094        path!("/test/.git").as_ref(),
21095        &[
21096            ("file-1", "one\n".into()),
21097            ("file-2", "two\n".into()),
21098            ("file-3", "three\n".into()),
21099        ],
21100        "deadbeef",
21101    );
21102
21103    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21104    let mut buffers = vec![];
21105    for i in 1..=3 {
21106        let buffer = project
21107            .update(cx, |project, cx| {
21108                let path = format!(path!("/test/file-{}"), i);
21109                project.open_local_buffer(path, cx)
21110            })
21111            .await
21112            .unwrap();
21113        buffers.push(buffer);
21114    }
21115
21116    let multibuffer = cx.new(|cx| {
21117        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21118        multibuffer.set_all_diff_hunks_expanded(cx);
21119        for buffer in &buffers {
21120            let snapshot = buffer.read(cx).snapshot();
21121            multibuffer.set_excerpts_for_path(
21122                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21123                buffer.clone(),
21124                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21125                2,
21126                cx,
21127            );
21128        }
21129        multibuffer
21130    });
21131
21132    let editor = cx.add_window(|window, cx| {
21133        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21134    });
21135    cx.run_until_parked();
21136
21137    let snapshot = editor
21138        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21139        .unwrap();
21140    let hunks = snapshot
21141        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21142        .map(|hunk| match hunk {
21143            DisplayDiffHunk::Unfolded {
21144                display_row_range, ..
21145            } => display_row_range,
21146            DisplayDiffHunk::Folded { .. } => unreachable!(),
21147        })
21148        .collect::<Vec<_>>();
21149    assert_eq!(
21150        hunks,
21151        [
21152            DisplayRow(2)..DisplayRow(4),
21153            DisplayRow(7)..DisplayRow(9),
21154            DisplayRow(12)..DisplayRow(14),
21155        ]
21156    );
21157}
21158
21159#[gpui::test]
21160async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21161    init_test(cx, |_| {});
21162
21163    let mut cx = EditorTestContext::new(cx).await;
21164    cx.set_head_text(indoc! { "
21165        one
21166        two
21167        three
21168        four
21169        five
21170        "
21171    });
21172    cx.set_index_text(indoc! { "
21173        one
21174        two
21175        three
21176        four
21177        five
21178        "
21179    });
21180    cx.set_state(indoc! {"
21181        one
21182        TWO
21183        ˇTHREE
21184        FOUR
21185        five
21186    "});
21187    cx.run_until_parked();
21188    cx.update_editor(|editor, window, cx| {
21189        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21190    });
21191    cx.run_until_parked();
21192    cx.assert_index_text(Some(indoc! {"
21193        one
21194        TWO
21195        THREE
21196        FOUR
21197        five
21198    "}));
21199    cx.set_state(indoc! { "
21200        one
21201        TWO
21202        ˇTHREE-HUNDRED
21203        FOUR
21204        five
21205    "});
21206    cx.run_until_parked();
21207    cx.update_editor(|editor, window, cx| {
21208        let snapshot = editor.snapshot(window, cx);
21209        let hunks = editor
21210            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21211            .collect::<Vec<_>>();
21212        assert_eq!(hunks.len(), 1);
21213        assert_eq!(
21214            hunks[0].status(),
21215            DiffHunkStatus {
21216                kind: DiffHunkStatusKind::Modified,
21217                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21218            }
21219        );
21220
21221        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21222    });
21223    cx.run_until_parked();
21224    cx.assert_index_text(Some(indoc! {"
21225        one
21226        TWO
21227        THREE-HUNDRED
21228        FOUR
21229        five
21230    "}));
21231}
21232
21233#[gpui::test]
21234fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21235    init_test(cx, |_| {});
21236
21237    let editor = cx.add_window(|window, cx| {
21238        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21239        build_editor(buffer, window, cx)
21240    });
21241
21242    let render_args = Arc::new(Mutex::new(None));
21243    let snapshot = editor
21244        .update(cx, |editor, window, cx| {
21245            let snapshot = editor.buffer().read(cx).snapshot(cx);
21246            let range =
21247                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21248
21249            struct RenderArgs {
21250                row: MultiBufferRow,
21251                folded: bool,
21252                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21253            }
21254
21255            let crease = Crease::inline(
21256                range,
21257                FoldPlaceholder::test(),
21258                {
21259                    let toggle_callback = render_args.clone();
21260                    move |row, folded, callback, _window, _cx| {
21261                        *toggle_callback.lock() = Some(RenderArgs {
21262                            row,
21263                            folded,
21264                            callback,
21265                        });
21266                        div()
21267                    }
21268                },
21269                |_row, _folded, _window, _cx| div(),
21270            );
21271
21272            editor.insert_creases(Some(crease), cx);
21273            let snapshot = editor.snapshot(window, cx);
21274            let _div =
21275                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21276            snapshot
21277        })
21278        .unwrap();
21279
21280    let render_args = render_args.lock().take().unwrap();
21281    assert_eq!(render_args.row, MultiBufferRow(1));
21282    assert!(!render_args.folded);
21283    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21284
21285    cx.update_window(*editor, |_, window, cx| {
21286        (render_args.callback)(true, window, cx)
21287    })
21288    .unwrap();
21289    let snapshot = editor
21290        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21291        .unwrap();
21292    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21293
21294    cx.update_window(*editor, |_, window, cx| {
21295        (render_args.callback)(false, window, cx)
21296    })
21297    .unwrap();
21298    let snapshot = editor
21299        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21300        .unwrap();
21301    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21302}
21303
21304#[gpui::test]
21305async fn test_input_text(cx: &mut TestAppContext) {
21306    init_test(cx, |_| {});
21307    let mut cx = EditorTestContext::new(cx).await;
21308
21309    cx.set_state(
21310        &r#"ˇone
21311        two
21312
21313        three
21314        fourˇ
21315        five
21316
21317        siˇx"#
21318            .unindent(),
21319    );
21320
21321    cx.dispatch_action(HandleInput(String::new()));
21322    cx.assert_editor_state(
21323        &r#"ˇone
21324        two
21325
21326        three
21327        fourˇ
21328        five
21329
21330        siˇx"#
21331            .unindent(),
21332    );
21333
21334    cx.dispatch_action(HandleInput("AAAA".to_string()));
21335    cx.assert_editor_state(
21336        &r#"AAAAˇone
21337        two
21338
21339        three
21340        fourAAAAˇ
21341        five
21342
21343        siAAAAˇx"#
21344            .unindent(),
21345    );
21346}
21347
21348#[gpui::test]
21349async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21350    init_test(cx, |_| {});
21351
21352    let mut cx = EditorTestContext::new(cx).await;
21353    cx.set_state(
21354        r#"let foo = 1;
21355let foo = 2;
21356let foo = 3;
21357let fooˇ = 4;
21358let foo = 5;
21359let foo = 6;
21360let foo = 7;
21361let foo = 8;
21362let foo = 9;
21363let foo = 10;
21364let foo = 11;
21365let foo = 12;
21366let foo = 13;
21367let foo = 14;
21368let foo = 15;"#,
21369    );
21370
21371    cx.update_editor(|e, window, cx| {
21372        assert_eq!(
21373            e.next_scroll_position,
21374            NextScrollCursorCenterTopBottom::Center,
21375            "Default next scroll direction is center",
21376        );
21377
21378        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21379        assert_eq!(
21380            e.next_scroll_position,
21381            NextScrollCursorCenterTopBottom::Top,
21382            "After center, next scroll direction should be top",
21383        );
21384
21385        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21386        assert_eq!(
21387            e.next_scroll_position,
21388            NextScrollCursorCenterTopBottom::Bottom,
21389            "After top, next scroll direction should be bottom",
21390        );
21391
21392        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21393        assert_eq!(
21394            e.next_scroll_position,
21395            NextScrollCursorCenterTopBottom::Center,
21396            "After bottom, scrolling should start over",
21397        );
21398
21399        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21400        assert_eq!(
21401            e.next_scroll_position,
21402            NextScrollCursorCenterTopBottom::Top,
21403            "Scrolling continues if retriggered fast enough"
21404        );
21405    });
21406
21407    cx.executor()
21408        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21409    cx.executor().run_until_parked();
21410    cx.update_editor(|e, _, _| {
21411        assert_eq!(
21412            e.next_scroll_position,
21413            NextScrollCursorCenterTopBottom::Center,
21414            "If scrolling is not triggered fast enough, it should reset"
21415        );
21416    });
21417}
21418
21419#[gpui::test]
21420async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21421    init_test(cx, |_| {});
21422    let mut cx = EditorLspTestContext::new_rust(
21423        lsp::ServerCapabilities {
21424            definition_provider: Some(lsp::OneOf::Left(true)),
21425            references_provider: Some(lsp::OneOf::Left(true)),
21426            ..lsp::ServerCapabilities::default()
21427        },
21428        cx,
21429    )
21430    .await;
21431
21432    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21433        let go_to_definition = cx
21434            .lsp
21435            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21436                move |params, _| async move {
21437                    if empty_go_to_definition {
21438                        Ok(None)
21439                    } else {
21440                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21441                            uri: params.text_document_position_params.text_document.uri,
21442                            range: lsp::Range::new(
21443                                lsp::Position::new(4, 3),
21444                                lsp::Position::new(4, 6),
21445                            ),
21446                        })))
21447                    }
21448                },
21449            );
21450        let references = cx
21451            .lsp
21452            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21453                Ok(Some(vec![lsp::Location {
21454                    uri: params.text_document_position.text_document.uri,
21455                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21456                }]))
21457            });
21458        (go_to_definition, references)
21459    };
21460
21461    cx.set_state(
21462        &r#"fn one() {
21463            let mut a = ˇtwo();
21464        }
21465
21466        fn two() {}"#
21467            .unindent(),
21468    );
21469    set_up_lsp_handlers(false, &mut cx);
21470    let navigated = cx
21471        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21472        .await
21473        .expect("Failed to navigate to definition");
21474    assert_eq!(
21475        navigated,
21476        Navigated::Yes,
21477        "Should have navigated to definition from the GetDefinition response"
21478    );
21479    cx.assert_editor_state(
21480        &r#"fn one() {
21481            let mut a = two();
21482        }
21483
21484        fn «twoˇ»() {}"#
21485            .unindent(),
21486    );
21487
21488    let editors = cx.update_workspace(|workspace, _, cx| {
21489        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21490    });
21491    cx.update_editor(|_, _, test_editor_cx| {
21492        assert_eq!(
21493            editors.len(),
21494            1,
21495            "Initially, only one, test, editor should be open in the workspace"
21496        );
21497        assert_eq!(
21498            test_editor_cx.entity(),
21499            editors.last().expect("Asserted len is 1").clone()
21500        );
21501    });
21502
21503    set_up_lsp_handlers(true, &mut cx);
21504    let navigated = cx
21505        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21506        .await
21507        .expect("Failed to navigate to lookup references");
21508    assert_eq!(
21509        navigated,
21510        Navigated::Yes,
21511        "Should have navigated to references as a fallback after empty GoToDefinition response"
21512    );
21513    // We should not change the selections in the existing file,
21514    // if opening another milti buffer with the references
21515    cx.assert_editor_state(
21516        &r#"fn one() {
21517            let mut a = two();
21518        }
21519
21520        fn «twoˇ»() {}"#
21521            .unindent(),
21522    );
21523    let editors = cx.update_workspace(|workspace, _, cx| {
21524        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21525    });
21526    cx.update_editor(|_, _, test_editor_cx| {
21527        assert_eq!(
21528            editors.len(),
21529            2,
21530            "After falling back to references search, we open a new editor with the results"
21531        );
21532        let references_fallback_text = editors
21533            .into_iter()
21534            .find(|new_editor| *new_editor != test_editor_cx.entity())
21535            .expect("Should have one non-test editor now")
21536            .read(test_editor_cx)
21537            .text(test_editor_cx);
21538        assert_eq!(
21539            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21540            "Should use the range from the references response and not the GoToDefinition one"
21541        );
21542    });
21543}
21544
21545#[gpui::test]
21546async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21547    init_test(cx, |_| {});
21548    cx.update(|cx| {
21549        let mut editor_settings = EditorSettings::get_global(cx).clone();
21550        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21551        EditorSettings::override_global(editor_settings, cx);
21552    });
21553    let mut cx = EditorLspTestContext::new_rust(
21554        lsp::ServerCapabilities {
21555            definition_provider: Some(lsp::OneOf::Left(true)),
21556            references_provider: Some(lsp::OneOf::Left(true)),
21557            ..lsp::ServerCapabilities::default()
21558        },
21559        cx,
21560    )
21561    .await;
21562    let original_state = r#"fn one() {
21563        let mut a = ˇtwo();
21564    }
21565
21566    fn two() {}"#
21567        .unindent();
21568    cx.set_state(&original_state);
21569
21570    let mut go_to_definition = cx
21571        .lsp
21572        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21573            move |_, _| async move { Ok(None) },
21574        );
21575    let _references = cx
21576        .lsp
21577        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21578            panic!("Should not call for references with no go to definition fallback")
21579        });
21580
21581    let navigated = cx
21582        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21583        .await
21584        .expect("Failed to navigate to lookup references");
21585    go_to_definition
21586        .next()
21587        .await
21588        .expect("Should have called the go_to_definition handler");
21589
21590    assert_eq!(
21591        navigated,
21592        Navigated::No,
21593        "Should have navigated to references as a fallback after empty GoToDefinition response"
21594    );
21595    cx.assert_editor_state(&original_state);
21596    let editors = cx.update_workspace(|workspace, _, cx| {
21597        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21598    });
21599    cx.update_editor(|_, _, _| {
21600        assert_eq!(
21601            editors.len(),
21602            1,
21603            "After unsuccessful fallback, no other editor should have been opened"
21604        );
21605    });
21606}
21607
21608#[gpui::test]
21609async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21610    init_test(cx, |_| {});
21611    let mut cx = EditorLspTestContext::new_rust(
21612        lsp::ServerCapabilities {
21613            references_provider: Some(lsp::OneOf::Left(true)),
21614            ..lsp::ServerCapabilities::default()
21615        },
21616        cx,
21617    )
21618    .await;
21619
21620    cx.set_state(
21621        &r#"
21622        fn one() {
21623            let mut a = two();
21624        }
21625
21626        fn ˇtwo() {}"#
21627            .unindent(),
21628    );
21629    cx.lsp
21630        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21631            Ok(Some(vec![
21632                lsp::Location {
21633                    uri: params.text_document_position.text_document.uri.clone(),
21634                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21635                },
21636                lsp::Location {
21637                    uri: params.text_document_position.text_document.uri,
21638                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21639                },
21640            ]))
21641        });
21642    let navigated = cx
21643        .update_editor(|editor, window, cx| {
21644            editor.find_all_references(&FindAllReferences, window, cx)
21645        })
21646        .unwrap()
21647        .await
21648        .expect("Failed to navigate to references");
21649    assert_eq!(
21650        navigated,
21651        Navigated::Yes,
21652        "Should have navigated to references from the FindAllReferences response"
21653    );
21654    cx.assert_editor_state(
21655        &r#"fn one() {
21656            let mut a = two();
21657        }
21658
21659        fn ˇtwo() {}"#
21660            .unindent(),
21661    );
21662
21663    let editors = cx.update_workspace(|workspace, _, cx| {
21664        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21665    });
21666    cx.update_editor(|_, _, _| {
21667        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21668    });
21669
21670    cx.set_state(
21671        &r#"fn one() {
21672            let mut a = ˇtwo();
21673        }
21674
21675        fn two() {}"#
21676            .unindent(),
21677    );
21678    let navigated = cx
21679        .update_editor(|editor, window, cx| {
21680            editor.find_all_references(&FindAllReferences, window, cx)
21681        })
21682        .unwrap()
21683        .await
21684        .expect("Failed to navigate to references");
21685    assert_eq!(
21686        navigated,
21687        Navigated::Yes,
21688        "Should have navigated to references from the FindAllReferences response"
21689    );
21690    cx.assert_editor_state(
21691        &r#"fn one() {
21692            let mut a = ˇtwo();
21693        }
21694
21695        fn two() {}"#
21696            .unindent(),
21697    );
21698    let editors = cx.update_workspace(|workspace, _, cx| {
21699        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21700    });
21701    cx.update_editor(|_, _, _| {
21702        assert_eq!(
21703            editors.len(),
21704            2,
21705            "should have re-used the previous multibuffer"
21706        );
21707    });
21708
21709    cx.set_state(
21710        &r#"fn one() {
21711            let mut a = ˇtwo();
21712        }
21713        fn three() {}
21714        fn two() {}"#
21715            .unindent(),
21716    );
21717    cx.lsp
21718        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21719            Ok(Some(vec![
21720                lsp::Location {
21721                    uri: params.text_document_position.text_document.uri.clone(),
21722                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21723                },
21724                lsp::Location {
21725                    uri: params.text_document_position.text_document.uri,
21726                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21727                },
21728            ]))
21729        });
21730    let navigated = cx
21731        .update_editor(|editor, window, cx| {
21732            editor.find_all_references(&FindAllReferences, window, cx)
21733        })
21734        .unwrap()
21735        .await
21736        .expect("Failed to navigate to references");
21737    assert_eq!(
21738        navigated,
21739        Navigated::Yes,
21740        "Should have navigated to references from the FindAllReferences response"
21741    );
21742    cx.assert_editor_state(
21743        &r#"fn one() {
21744                let mut a = ˇtwo();
21745            }
21746            fn three() {}
21747            fn two() {}"#
21748            .unindent(),
21749    );
21750    let editors = cx.update_workspace(|workspace, _, cx| {
21751        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21752    });
21753    cx.update_editor(|_, _, _| {
21754        assert_eq!(
21755            editors.len(),
21756            3,
21757            "should have used a new multibuffer as offsets changed"
21758        );
21759    });
21760}
21761#[gpui::test]
21762async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21763    init_test(cx, |_| {});
21764
21765    let language = Arc::new(Language::new(
21766        LanguageConfig::default(),
21767        Some(tree_sitter_rust::LANGUAGE.into()),
21768    ));
21769
21770    let text = r#"
21771        #[cfg(test)]
21772        mod tests() {
21773            #[test]
21774            fn runnable_1() {
21775                let a = 1;
21776            }
21777
21778            #[test]
21779            fn runnable_2() {
21780                let a = 1;
21781                let b = 2;
21782            }
21783        }
21784    "#
21785    .unindent();
21786
21787    let fs = FakeFs::new(cx.executor());
21788    fs.insert_file("/file.rs", Default::default()).await;
21789
21790    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21791    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21792    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21793    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21794    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21795
21796    let editor = cx.new_window_entity(|window, cx| {
21797        Editor::new(
21798            EditorMode::full(),
21799            multi_buffer,
21800            Some(project.clone()),
21801            window,
21802            cx,
21803        )
21804    });
21805
21806    editor.update_in(cx, |editor, window, cx| {
21807        let snapshot = editor.buffer().read(cx).snapshot(cx);
21808        editor.tasks.insert(
21809            (buffer.read(cx).remote_id(), 3),
21810            RunnableTasks {
21811                templates: vec![],
21812                offset: snapshot.anchor_before(43),
21813                column: 0,
21814                extra_variables: HashMap::default(),
21815                context_range: BufferOffset(43)..BufferOffset(85),
21816            },
21817        );
21818        editor.tasks.insert(
21819            (buffer.read(cx).remote_id(), 8),
21820            RunnableTasks {
21821                templates: vec![],
21822                offset: snapshot.anchor_before(86),
21823                column: 0,
21824                extra_variables: HashMap::default(),
21825                context_range: BufferOffset(86)..BufferOffset(191),
21826            },
21827        );
21828
21829        // Test finding task when cursor is inside function body
21830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21831            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21832        });
21833        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21834        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21835
21836        // Test finding task when cursor is on function name
21837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21838            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21839        });
21840        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21841        assert_eq!(row, 8, "Should find task when cursor is on function name");
21842    });
21843}
21844
21845#[gpui::test]
21846async fn test_folding_buffers(cx: &mut TestAppContext) {
21847    init_test(cx, |_| {});
21848
21849    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21850    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21851    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21852
21853    let fs = FakeFs::new(cx.executor());
21854    fs.insert_tree(
21855        path!("/a"),
21856        json!({
21857            "first.rs": sample_text_1,
21858            "second.rs": sample_text_2,
21859            "third.rs": sample_text_3,
21860        }),
21861    )
21862    .await;
21863    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21864    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21865    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21866    let worktree = project.update(cx, |project, cx| {
21867        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21868        assert_eq!(worktrees.len(), 1);
21869        worktrees.pop().unwrap()
21870    });
21871    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21872
21873    let buffer_1 = project
21874        .update(cx, |project, cx| {
21875            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21876        })
21877        .await
21878        .unwrap();
21879    let buffer_2 = project
21880        .update(cx, |project, cx| {
21881            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21882        })
21883        .await
21884        .unwrap();
21885    let buffer_3 = project
21886        .update(cx, |project, cx| {
21887            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21888        })
21889        .await
21890        .unwrap();
21891
21892    let multi_buffer = cx.new(|cx| {
21893        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21894        multi_buffer.push_excerpts(
21895            buffer_1.clone(),
21896            [
21897                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21898                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21899                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21900            ],
21901            cx,
21902        );
21903        multi_buffer.push_excerpts(
21904            buffer_2.clone(),
21905            [
21906                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21907                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21908                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21909            ],
21910            cx,
21911        );
21912        multi_buffer.push_excerpts(
21913            buffer_3.clone(),
21914            [
21915                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21916                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21917                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21918            ],
21919            cx,
21920        );
21921        multi_buffer
21922    });
21923    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21924        Editor::new(
21925            EditorMode::full(),
21926            multi_buffer.clone(),
21927            Some(project.clone()),
21928            window,
21929            cx,
21930        )
21931    });
21932
21933    assert_eq!(
21934        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21935        "\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",
21936    );
21937
21938    multi_buffer_editor.update(cx, |editor, cx| {
21939        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21940    });
21941    assert_eq!(
21942        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21943        "\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",
21944        "After folding the first buffer, its text should not be displayed"
21945    );
21946
21947    multi_buffer_editor.update(cx, |editor, cx| {
21948        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21949    });
21950    assert_eq!(
21951        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21952        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21953        "After folding the second buffer, its text should not be displayed"
21954    );
21955
21956    multi_buffer_editor.update(cx, |editor, cx| {
21957        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21958    });
21959    assert_eq!(
21960        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21961        "\n\n\n\n\n",
21962        "After folding the third buffer, its text should not be displayed"
21963    );
21964
21965    // Emulate selection inside the fold logic, that should work
21966    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21967        editor
21968            .snapshot(window, cx)
21969            .next_line_boundary(Point::new(0, 4));
21970    });
21971
21972    multi_buffer_editor.update(cx, |editor, cx| {
21973        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21974    });
21975    assert_eq!(
21976        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21977        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21978        "After unfolding the second buffer, its text should be displayed"
21979    );
21980
21981    // Typing inside of buffer 1 causes that buffer to be unfolded.
21982    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21983        assert_eq!(
21984            multi_buffer
21985                .read(cx)
21986                .snapshot(cx)
21987                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21988                .collect::<String>(),
21989            "bbbb"
21990        );
21991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21992            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21993        });
21994        editor.handle_input("B", window, cx);
21995    });
21996
21997    assert_eq!(
21998        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21999        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22000        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22001    );
22002
22003    multi_buffer_editor.update(cx, |editor, cx| {
22004        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22005    });
22006    assert_eq!(
22007        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22008        "\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",
22009        "After unfolding the all buffers, all original text should be displayed"
22010    );
22011}
22012
22013#[gpui::test]
22014async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22015    init_test(cx, |_| {});
22016
22017    let sample_text_1 = "1111\n2222\n3333".to_string();
22018    let sample_text_2 = "4444\n5555\n6666".to_string();
22019    let sample_text_3 = "7777\n8888\n9999".to_string();
22020
22021    let fs = FakeFs::new(cx.executor());
22022    fs.insert_tree(
22023        path!("/a"),
22024        json!({
22025            "first.rs": sample_text_1,
22026            "second.rs": sample_text_2,
22027            "third.rs": sample_text_3,
22028        }),
22029    )
22030    .await;
22031    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22032    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22033    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22034    let worktree = project.update(cx, |project, cx| {
22035        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22036        assert_eq!(worktrees.len(), 1);
22037        worktrees.pop().unwrap()
22038    });
22039    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22040
22041    let buffer_1 = project
22042        .update(cx, |project, cx| {
22043            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22044        })
22045        .await
22046        .unwrap();
22047    let buffer_2 = project
22048        .update(cx, |project, cx| {
22049            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22050        })
22051        .await
22052        .unwrap();
22053    let buffer_3 = project
22054        .update(cx, |project, cx| {
22055            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22056        })
22057        .await
22058        .unwrap();
22059
22060    let multi_buffer = cx.new(|cx| {
22061        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22062        multi_buffer.push_excerpts(
22063            buffer_1.clone(),
22064            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22065            cx,
22066        );
22067        multi_buffer.push_excerpts(
22068            buffer_2.clone(),
22069            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22070            cx,
22071        );
22072        multi_buffer.push_excerpts(
22073            buffer_3.clone(),
22074            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22075            cx,
22076        );
22077        multi_buffer
22078    });
22079
22080    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22081        Editor::new(
22082            EditorMode::full(),
22083            multi_buffer,
22084            Some(project.clone()),
22085            window,
22086            cx,
22087        )
22088    });
22089
22090    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22091    assert_eq!(
22092        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22093        full_text,
22094    );
22095
22096    multi_buffer_editor.update(cx, |editor, cx| {
22097        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22098    });
22099    assert_eq!(
22100        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22101        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22102        "After folding the first buffer, its text should not be displayed"
22103    );
22104
22105    multi_buffer_editor.update(cx, |editor, cx| {
22106        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22107    });
22108
22109    assert_eq!(
22110        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22111        "\n\n\n\n\n\n7777\n8888\n9999",
22112        "After folding the second buffer, its text should not be displayed"
22113    );
22114
22115    multi_buffer_editor.update(cx, |editor, cx| {
22116        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22117    });
22118    assert_eq!(
22119        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22120        "\n\n\n\n\n",
22121        "After folding the third buffer, its text should not be displayed"
22122    );
22123
22124    multi_buffer_editor.update(cx, |editor, cx| {
22125        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22126    });
22127    assert_eq!(
22128        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22129        "\n\n\n\n4444\n5555\n6666\n\n",
22130        "After unfolding the second buffer, its text should be displayed"
22131    );
22132
22133    multi_buffer_editor.update(cx, |editor, cx| {
22134        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22135    });
22136    assert_eq!(
22137        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22138        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22139        "After unfolding the first buffer, its text should be displayed"
22140    );
22141
22142    multi_buffer_editor.update(cx, |editor, cx| {
22143        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22144    });
22145    assert_eq!(
22146        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22147        full_text,
22148        "After unfolding all buffers, all original text should be displayed"
22149    );
22150}
22151
22152#[gpui::test]
22153async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22154    init_test(cx, |_| {});
22155
22156    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22157
22158    let fs = FakeFs::new(cx.executor());
22159    fs.insert_tree(
22160        path!("/a"),
22161        json!({
22162            "main.rs": sample_text,
22163        }),
22164    )
22165    .await;
22166    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22167    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22168    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22169    let worktree = project.update(cx, |project, cx| {
22170        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22171        assert_eq!(worktrees.len(), 1);
22172        worktrees.pop().unwrap()
22173    });
22174    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22175
22176    let buffer_1 = project
22177        .update(cx, |project, cx| {
22178            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22179        })
22180        .await
22181        .unwrap();
22182
22183    let multi_buffer = cx.new(|cx| {
22184        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22185        multi_buffer.push_excerpts(
22186            buffer_1.clone(),
22187            [ExcerptRange::new(
22188                Point::new(0, 0)
22189                    ..Point::new(
22190                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22191                        0,
22192                    ),
22193            )],
22194            cx,
22195        );
22196        multi_buffer
22197    });
22198    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22199        Editor::new(
22200            EditorMode::full(),
22201            multi_buffer,
22202            Some(project.clone()),
22203            window,
22204            cx,
22205        )
22206    });
22207
22208    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22209    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22210        enum TestHighlight {}
22211        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22212        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22213        editor.highlight_text::<TestHighlight>(
22214            vec![highlight_range.clone()],
22215            HighlightStyle::color(Hsla::green()),
22216            cx,
22217        );
22218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22219            s.select_ranges(Some(highlight_range))
22220        });
22221    });
22222
22223    let full_text = format!("\n\n{sample_text}");
22224    assert_eq!(
22225        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22226        full_text,
22227    );
22228}
22229
22230#[gpui::test]
22231async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22232    init_test(cx, |_| {});
22233    cx.update(|cx| {
22234        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22235            "keymaps/default-linux.json",
22236            cx,
22237        )
22238        .unwrap();
22239        cx.bind_keys(default_key_bindings);
22240    });
22241
22242    let (editor, cx) = cx.add_window_view(|window, cx| {
22243        let multi_buffer = MultiBuffer::build_multi(
22244            [
22245                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22246                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22247                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22248                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22249            ],
22250            cx,
22251        );
22252        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22253
22254        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22255        // fold all but the second buffer, so that we test navigating between two
22256        // adjacent folded buffers, as well as folded buffers at the start and
22257        // end the multibuffer
22258        editor.fold_buffer(buffer_ids[0], cx);
22259        editor.fold_buffer(buffer_ids[2], cx);
22260        editor.fold_buffer(buffer_ids[3], cx);
22261
22262        editor
22263    });
22264    cx.simulate_resize(size(px(1000.), px(1000.)));
22265
22266    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22267    cx.assert_excerpts_with_selections(indoc! {"
22268        [EXCERPT]
22269        ˇ[FOLDED]
22270        [EXCERPT]
22271        a1
22272        b1
22273        [EXCERPT]
22274        [FOLDED]
22275        [EXCERPT]
22276        [FOLDED]
22277        "
22278    });
22279    cx.simulate_keystroke("down");
22280    cx.assert_excerpts_with_selections(indoc! {"
22281        [EXCERPT]
22282        [FOLDED]
22283        [EXCERPT]
22284        ˇa1
22285        b1
22286        [EXCERPT]
22287        [FOLDED]
22288        [EXCERPT]
22289        [FOLDED]
22290        "
22291    });
22292    cx.simulate_keystroke("down");
22293    cx.assert_excerpts_with_selections(indoc! {"
22294        [EXCERPT]
22295        [FOLDED]
22296        [EXCERPT]
22297        a1
22298        ˇb1
22299        [EXCERPT]
22300        [FOLDED]
22301        [EXCERPT]
22302        [FOLDED]
22303        "
22304    });
22305    cx.simulate_keystroke("down");
22306    cx.assert_excerpts_with_selections(indoc! {"
22307        [EXCERPT]
22308        [FOLDED]
22309        [EXCERPT]
22310        a1
22311        b1
22312        ˇ[EXCERPT]
22313        [FOLDED]
22314        [EXCERPT]
22315        [FOLDED]
22316        "
22317    });
22318    cx.simulate_keystroke("down");
22319    cx.assert_excerpts_with_selections(indoc! {"
22320        [EXCERPT]
22321        [FOLDED]
22322        [EXCERPT]
22323        a1
22324        b1
22325        [EXCERPT]
22326        ˇ[FOLDED]
22327        [EXCERPT]
22328        [FOLDED]
22329        "
22330    });
22331    for _ in 0..5 {
22332        cx.simulate_keystroke("down");
22333        cx.assert_excerpts_with_selections(indoc! {"
22334            [EXCERPT]
22335            [FOLDED]
22336            [EXCERPT]
22337            a1
22338            b1
22339            [EXCERPT]
22340            [FOLDED]
22341            [EXCERPT]
22342            ˇ[FOLDED]
22343            "
22344        });
22345    }
22346
22347    cx.simulate_keystroke("up");
22348    cx.assert_excerpts_with_selections(indoc! {"
22349        [EXCERPT]
22350        [FOLDED]
22351        [EXCERPT]
22352        a1
22353        b1
22354        [EXCERPT]
22355        ˇ[FOLDED]
22356        [EXCERPT]
22357        [FOLDED]
22358        "
22359    });
22360    cx.simulate_keystroke("up");
22361    cx.assert_excerpts_with_selections(indoc! {"
22362        [EXCERPT]
22363        [FOLDED]
22364        [EXCERPT]
22365        a1
22366        b1
22367        ˇ[EXCERPT]
22368        [FOLDED]
22369        [EXCERPT]
22370        [FOLDED]
22371        "
22372    });
22373    cx.simulate_keystroke("up");
22374    cx.assert_excerpts_with_selections(indoc! {"
22375        [EXCERPT]
22376        [FOLDED]
22377        [EXCERPT]
22378        a1
22379        ˇb1
22380        [EXCERPT]
22381        [FOLDED]
22382        [EXCERPT]
22383        [FOLDED]
22384        "
22385    });
22386    cx.simulate_keystroke("up");
22387    cx.assert_excerpts_with_selections(indoc! {"
22388        [EXCERPT]
22389        [FOLDED]
22390        [EXCERPT]
22391        ˇa1
22392        b1
22393        [EXCERPT]
22394        [FOLDED]
22395        [EXCERPT]
22396        [FOLDED]
22397        "
22398    });
22399    for _ in 0..5 {
22400        cx.simulate_keystroke("up");
22401        cx.assert_excerpts_with_selections(indoc! {"
22402            [EXCERPT]
22403            ˇ[FOLDED]
22404            [EXCERPT]
22405            a1
22406            b1
22407            [EXCERPT]
22408            [FOLDED]
22409            [EXCERPT]
22410            [FOLDED]
22411            "
22412        });
22413    }
22414}
22415
22416#[gpui::test]
22417async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22418    init_test(cx, |_| {});
22419
22420    // Simple insertion
22421    assert_highlighted_edits(
22422        "Hello, world!",
22423        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22424        true,
22425        cx,
22426        |highlighted_edits, cx| {
22427            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22428            assert_eq!(highlighted_edits.highlights.len(), 1);
22429            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22430            assert_eq!(
22431                highlighted_edits.highlights[0].1.background_color,
22432                Some(cx.theme().status().created_background)
22433            );
22434        },
22435    )
22436    .await;
22437
22438    // Replacement
22439    assert_highlighted_edits(
22440        "This is a test.",
22441        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22442        false,
22443        cx,
22444        |highlighted_edits, cx| {
22445            assert_eq!(highlighted_edits.text, "That is a test.");
22446            assert_eq!(highlighted_edits.highlights.len(), 1);
22447            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22448            assert_eq!(
22449                highlighted_edits.highlights[0].1.background_color,
22450                Some(cx.theme().status().created_background)
22451            );
22452        },
22453    )
22454    .await;
22455
22456    // Multiple edits
22457    assert_highlighted_edits(
22458        "Hello, world!",
22459        vec![
22460            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22461            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22462        ],
22463        false,
22464        cx,
22465        |highlighted_edits, cx| {
22466            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22467            assert_eq!(highlighted_edits.highlights.len(), 2);
22468            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22469            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22470            assert_eq!(
22471                highlighted_edits.highlights[0].1.background_color,
22472                Some(cx.theme().status().created_background)
22473            );
22474            assert_eq!(
22475                highlighted_edits.highlights[1].1.background_color,
22476                Some(cx.theme().status().created_background)
22477            );
22478        },
22479    )
22480    .await;
22481
22482    // Multiple lines with edits
22483    assert_highlighted_edits(
22484        "First line\nSecond line\nThird line\nFourth line",
22485        vec![
22486            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22487            (
22488                Point::new(2, 0)..Point::new(2, 10),
22489                "New third line".to_string(),
22490            ),
22491            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22492        ],
22493        false,
22494        cx,
22495        |highlighted_edits, cx| {
22496            assert_eq!(
22497                highlighted_edits.text,
22498                "Second modified\nNew third line\nFourth updated line"
22499            );
22500            assert_eq!(highlighted_edits.highlights.len(), 3);
22501            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22502            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22503            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22504            for highlight in &highlighted_edits.highlights {
22505                assert_eq!(
22506                    highlight.1.background_color,
22507                    Some(cx.theme().status().created_background)
22508                );
22509            }
22510        },
22511    )
22512    .await;
22513}
22514
22515#[gpui::test]
22516async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22517    init_test(cx, |_| {});
22518
22519    // Deletion
22520    assert_highlighted_edits(
22521        "Hello, world!",
22522        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22523        true,
22524        cx,
22525        |highlighted_edits, cx| {
22526            assert_eq!(highlighted_edits.text, "Hello, world!");
22527            assert_eq!(highlighted_edits.highlights.len(), 1);
22528            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22529            assert_eq!(
22530                highlighted_edits.highlights[0].1.background_color,
22531                Some(cx.theme().status().deleted_background)
22532            );
22533        },
22534    )
22535    .await;
22536
22537    // Insertion
22538    assert_highlighted_edits(
22539        "Hello, world!",
22540        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22541        true,
22542        cx,
22543        |highlighted_edits, cx| {
22544            assert_eq!(highlighted_edits.highlights.len(), 1);
22545            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22546            assert_eq!(
22547                highlighted_edits.highlights[0].1.background_color,
22548                Some(cx.theme().status().created_background)
22549            );
22550        },
22551    )
22552    .await;
22553}
22554
22555async fn assert_highlighted_edits(
22556    text: &str,
22557    edits: Vec<(Range<Point>, String)>,
22558    include_deletions: bool,
22559    cx: &mut TestAppContext,
22560    assertion_fn: impl Fn(HighlightedText, &App),
22561) {
22562    let window = cx.add_window(|window, cx| {
22563        let buffer = MultiBuffer::build_simple(text, cx);
22564        Editor::new(EditorMode::full(), buffer, None, window, cx)
22565    });
22566    let cx = &mut VisualTestContext::from_window(*window, cx);
22567
22568    let (buffer, snapshot) = window
22569        .update(cx, |editor, _window, cx| {
22570            (
22571                editor.buffer().clone(),
22572                editor.buffer().read(cx).snapshot(cx),
22573            )
22574        })
22575        .unwrap();
22576
22577    let edits = edits
22578        .into_iter()
22579        .map(|(range, edit)| {
22580            (
22581                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22582                edit,
22583            )
22584        })
22585        .collect::<Vec<_>>();
22586
22587    let text_anchor_edits = edits
22588        .clone()
22589        .into_iter()
22590        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22591        .collect::<Vec<_>>();
22592
22593    let edit_preview = window
22594        .update(cx, |_, _window, cx| {
22595            buffer
22596                .read(cx)
22597                .as_singleton()
22598                .unwrap()
22599                .read(cx)
22600                .preview_edits(text_anchor_edits.into(), cx)
22601        })
22602        .unwrap()
22603        .await;
22604
22605    cx.update(|_window, cx| {
22606        let highlighted_edits = edit_prediction_edit_text(
22607            snapshot.as_singleton().unwrap().2,
22608            &edits,
22609            &edit_preview,
22610            include_deletions,
22611            cx,
22612        );
22613        assertion_fn(highlighted_edits, cx)
22614    });
22615}
22616
22617#[track_caller]
22618fn assert_breakpoint(
22619    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22620    path: &Arc<Path>,
22621    expected: Vec<(u32, Breakpoint)>,
22622) {
22623    if expected.is_empty() {
22624        assert!(!breakpoints.contains_key(path), "{}", path.display());
22625    } else {
22626        let mut breakpoint = breakpoints
22627            .get(path)
22628            .unwrap()
22629            .iter()
22630            .map(|breakpoint| {
22631                (
22632                    breakpoint.row,
22633                    Breakpoint {
22634                        message: breakpoint.message.clone(),
22635                        state: breakpoint.state,
22636                        condition: breakpoint.condition.clone(),
22637                        hit_condition: breakpoint.hit_condition.clone(),
22638                    },
22639                )
22640            })
22641            .collect::<Vec<_>>();
22642
22643        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22644
22645        assert_eq!(expected, breakpoint);
22646    }
22647}
22648
22649fn add_log_breakpoint_at_cursor(
22650    editor: &mut Editor,
22651    log_message: &str,
22652    window: &mut Window,
22653    cx: &mut Context<Editor>,
22654) {
22655    let (anchor, bp) = editor
22656        .breakpoints_at_cursors(window, cx)
22657        .first()
22658        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22659        .unwrap_or_else(|| {
22660            let cursor_position: Point = editor.selections.newest(cx).head();
22661
22662            let breakpoint_position = editor
22663                .snapshot(window, cx)
22664                .display_snapshot
22665                .buffer_snapshot()
22666                .anchor_before(Point::new(cursor_position.row, 0));
22667
22668            (breakpoint_position, Breakpoint::new_log(log_message))
22669        });
22670
22671    editor.edit_breakpoint_at_anchor(
22672        anchor,
22673        bp,
22674        BreakpointEditAction::EditLogMessage(log_message.into()),
22675        cx,
22676    );
22677}
22678
22679#[gpui::test]
22680async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22681    init_test(cx, |_| {});
22682
22683    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22684    let fs = FakeFs::new(cx.executor());
22685    fs.insert_tree(
22686        path!("/a"),
22687        json!({
22688            "main.rs": sample_text,
22689        }),
22690    )
22691    .await;
22692    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22693    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22694    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22695
22696    let fs = FakeFs::new(cx.executor());
22697    fs.insert_tree(
22698        path!("/a"),
22699        json!({
22700            "main.rs": sample_text,
22701        }),
22702    )
22703    .await;
22704    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22705    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22706    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22707    let worktree_id = workspace
22708        .update(cx, |workspace, _window, cx| {
22709            workspace.project().update(cx, |project, cx| {
22710                project.worktrees(cx).next().unwrap().read(cx).id()
22711            })
22712        })
22713        .unwrap();
22714
22715    let buffer = project
22716        .update(cx, |project, cx| {
22717            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22718        })
22719        .await
22720        .unwrap();
22721
22722    let (editor, cx) = cx.add_window_view(|window, cx| {
22723        Editor::new(
22724            EditorMode::full(),
22725            MultiBuffer::build_from_buffer(buffer, cx),
22726            Some(project.clone()),
22727            window,
22728            cx,
22729        )
22730    });
22731
22732    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22733    let abs_path = project.read_with(cx, |project, cx| {
22734        project
22735            .absolute_path(&project_path, cx)
22736            .map(Arc::from)
22737            .unwrap()
22738    });
22739
22740    // assert we can add breakpoint on the first line
22741    editor.update_in(cx, |editor, window, cx| {
22742        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22743        editor.move_to_end(&MoveToEnd, window, cx);
22744        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22745    });
22746
22747    let breakpoints = editor.update(cx, |editor, cx| {
22748        editor
22749            .breakpoint_store()
22750            .as_ref()
22751            .unwrap()
22752            .read(cx)
22753            .all_source_breakpoints(cx)
22754    });
22755
22756    assert_eq!(1, breakpoints.len());
22757    assert_breakpoint(
22758        &breakpoints,
22759        &abs_path,
22760        vec![
22761            (0, Breakpoint::new_standard()),
22762            (3, Breakpoint::new_standard()),
22763        ],
22764    );
22765
22766    editor.update_in(cx, |editor, window, cx| {
22767        editor.move_to_beginning(&MoveToBeginning, window, cx);
22768        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22769    });
22770
22771    let breakpoints = editor.update(cx, |editor, cx| {
22772        editor
22773            .breakpoint_store()
22774            .as_ref()
22775            .unwrap()
22776            .read(cx)
22777            .all_source_breakpoints(cx)
22778    });
22779
22780    assert_eq!(1, breakpoints.len());
22781    assert_breakpoint(
22782        &breakpoints,
22783        &abs_path,
22784        vec![(3, Breakpoint::new_standard())],
22785    );
22786
22787    editor.update_in(cx, |editor, window, cx| {
22788        editor.move_to_end(&MoveToEnd, window, cx);
22789        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22790    });
22791
22792    let breakpoints = editor.update(cx, |editor, cx| {
22793        editor
22794            .breakpoint_store()
22795            .as_ref()
22796            .unwrap()
22797            .read(cx)
22798            .all_source_breakpoints(cx)
22799    });
22800
22801    assert_eq!(0, breakpoints.len());
22802    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22803}
22804
22805#[gpui::test]
22806async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22807    init_test(cx, |_| {});
22808
22809    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22810
22811    let fs = FakeFs::new(cx.executor());
22812    fs.insert_tree(
22813        path!("/a"),
22814        json!({
22815            "main.rs": sample_text,
22816        }),
22817    )
22818    .await;
22819    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22820    let (workspace, cx) =
22821        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22822
22823    let worktree_id = workspace.update(cx, |workspace, cx| {
22824        workspace.project().update(cx, |project, cx| {
22825            project.worktrees(cx).next().unwrap().read(cx).id()
22826        })
22827    });
22828
22829    let buffer = project
22830        .update(cx, |project, cx| {
22831            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22832        })
22833        .await
22834        .unwrap();
22835
22836    let (editor, cx) = cx.add_window_view(|window, cx| {
22837        Editor::new(
22838            EditorMode::full(),
22839            MultiBuffer::build_from_buffer(buffer, cx),
22840            Some(project.clone()),
22841            window,
22842            cx,
22843        )
22844    });
22845
22846    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22847    let abs_path = project.read_with(cx, |project, cx| {
22848        project
22849            .absolute_path(&project_path, cx)
22850            .map(Arc::from)
22851            .unwrap()
22852    });
22853
22854    editor.update_in(cx, |editor, window, cx| {
22855        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22856    });
22857
22858    let breakpoints = editor.update(cx, |editor, cx| {
22859        editor
22860            .breakpoint_store()
22861            .as_ref()
22862            .unwrap()
22863            .read(cx)
22864            .all_source_breakpoints(cx)
22865    });
22866
22867    assert_breakpoint(
22868        &breakpoints,
22869        &abs_path,
22870        vec![(0, Breakpoint::new_log("hello world"))],
22871    );
22872
22873    // Removing a log message from a log breakpoint should remove it
22874    editor.update_in(cx, |editor, window, cx| {
22875        add_log_breakpoint_at_cursor(editor, "", window, cx);
22876    });
22877
22878    let breakpoints = editor.update(cx, |editor, cx| {
22879        editor
22880            .breakpoint_store()
22881            .as_ref()
22882            .unwrap()
22883            .read(cx)
22884            .all_source_breakpoints(cx)
22885    });
22886
22887    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22888
22889    editor.update_in(cx, |editor, window, cx| {
22890        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22891        editor.move_to_end(&MoveToEnd, window, cx);
22892        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22893        // Not adding a log message to a standard breakpoint shouldn't remove it
22894        add_log_breakpoint_at_cursor(editor, "", window, cx);
22895    });
22896
22897    let breakpoints = editor.update(cx, |editor, cx| {
22898        editor
22899            .breakpoint_store()
22900            .as_ref()
22901            .unwrap()
22902            .read(cx)
22903            .all_source_breakpoints(cx)
22904    });
22905
22906    assert_breakpoint(
22907        &breakpoints,
22908        &abs_path,
22909        vec![
22910            (0, Breakpoint::new_standard()),
22911            (3, Breakpoint::new_standard()),
22912        ],
22913    );
22914
22915    editor.update_in(cx, |editor, window, cx| {
22916        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22917    });
22918
22919    let breakpoints = editor.update(cx, |editor, cx| {
22920        editor
22921            .breakpoint_store()
22922            .as_ref()
22923            .unwrap()
22924            .read(cx)
22925            .all_source_breakpoints(cx)
22926    });
22927
22928    assert_breakpoint(
22929        &breakpoints,
22930        &abs_path,
22931        vec![
22932            (0, Breakpoint::new_standard()),
22933            (3, Breakpoint::new_log("hello world")),
22934        ],
22935    );
22936
22937    editor.update_in(cx, |editor, window, cx| {
22938        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22939    });
22940
22941    let breakpoints = editor.update(cx, |editor, cx| {
22942        editor
22943            .breakpoint_store()
22944            .as_ref()
22945            .unwrap()
22946            .read(cx)
22947            .all_source_breakpoints(cx)
22948    });
22949
22950    assert_breakpoint(
22951        &breakpoints,
22952        &abs_path,
22953        vec![
22954            (0, Breakpoint::new_standard()),
22955            (3, Breakpoint::new_log("hello Earth!!")),
22956        ],
22957    );
22958}
22959
22960/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22961/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22962/// or when breakpoints were placed out of order. This tests for a regression too
22963#[gpui::test]
22964async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22965    init_test(cx, |_| {});
22966
22967    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22968    let fs = FakeFs::new(cx.executor());
22969    fs.insert_tree(
22970        path!("/a"),
22971        json!({
22972            "main.rs": sample_text,
22973        }),
22974    )
22975    .await;
22976    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22977    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22978    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22979
22980    let fs = FakeFs::new(cx.executor());
22981    fs.insert_tree(
22982        path!("/a"),
22983        json!({
22984            "main.rs": sample_text,
22985        }),
22986    )
22987    .await;
22988    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22989    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22990    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22991    let worktree_id = workspace
22992        .update(cx, |workspace, _window, cx| {
22993            workspace.project().update(cx, |project, cx| {
22994                project.worktrees(cx).next().unwrap().read(cx).id()
22995            })
22996        })
22997        .unwrap();
22998
22999    let buffer = project
23000        .update(cx, |project, cx| {
23001            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23002        })
23003        .await
23004        .unwrap();
23005
23006    let (editor, cx) = cx.add_window_view(|window, cx| {
23007        Editor::new(
23008            EditorMode::full(),
23009            MultiBuffer::build_from_buffer(buffer, cx),
23010            Some(project.clone()),
23011            window,
23012            cx,
23013        )
23014    });
23015
23016    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23017    let abs_path = project.read_with(cx, |project, cx| {
23018        project
23019            .absolute_path(&project_path, cx)
23020            .map(Arc::from)
23021            .unwrap()
23022    });
23023
23024    // assert we can add breakpoint on the first line
23025    editor.update_in(cx, |editor, window, cx| {
23026        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23027        editor.move_to_end(&MoveToEnd, window, cx);
23028        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23029        editor.move_up(&MoveUp, window, cx);
23030        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23031    });
23032
23033    let breakpoints = editor.update(cx, |editor, cx| {
23034        editor
23035            .breakpoint_store()
23036            .as_ref()
23037            .unwrap()
23038            .read(cx)
23039            .all_source_breakpoints(cx)
23040    });
23041
23042    assert_eq!(1, breakpoints.len());
23043    assert_breakpoint(
23044        &breakpoints,
23045        &abs_path,
23046        vec![
23047            (0, Breakpoint::new_standard()),
23048            (2, Breakpoint::new_standard()),
23049            (3, Breakpoint::new_standard()),
23050        ],
23051    );
23052
23053    editor.update_in(cx, |editor, window, cx| {
23054        editor.move_to_beginning(&MoveToBeginning, window, cx);
23055        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23056        editor.move_to_end(&MoveToEnd, window, cx);
23057        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23058        // Disabling a breakpoint that doesn't exist should do nothing
23059        editor.move_up(&MoveUp, window, cx);
23060        editor.move_up(&MoveUp, window, cx);
23061        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23062    });
23063
23064    let breakpoints = editor.update(cx, |editor, cx| {
23065        editor
23066            .breakpoint_store()
23067            .as_ref()
23068            .unwrap()
23069            .read(cx)
23070            .all_source_breakpoints(cx)
23071    });
23072
23073    let disable_breakpoint = {
23074        let mut bp = Breakpoint::new_standard();
23075        bp.state = BreakpointState::Disabled;
23076        bp
23077    };
23078
23079    assert_eq!(1, breakpoints.len());
23080    assert_breakpoint(
23081        &breakpoints,
23082        &abs_path,
23083        vec![
23084            (0, disable_breakpoint.clone()),
23085            (2, Breakpoint::new_standard()),
23086            (3, disable_breakpoint.clone()),
23087        ],
23088    );
23089
23090    editor.update_in(cx, |editor, window, cx| {
23091        editor.move_to_beginning(&MoveToBeginning, window, cx);
23092        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23093        editor.move_to_end(&MoveToEnd, window, cx);
23094        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23095        editor.move_up(&MoveUp, window, cx);
23096        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23097    });
23098
23099    let breakpoints = editor.update(cx, |editor, cx| {
23100        editor
23101            .breakpoint_store()
23102            .as_ref()
23103            .unwrap()
23104            .read(cx)
23105            .all_source_breakpoints(cx)
23106    });
23107
23108    assert_eq!(1, breakpoints.len());
23109    assert_breakpoint(
23110        &breakpoints,
23111        &abs_path,
23112        vec![
23113            (0, Breakpoint::new_standard()),
23114            (2, disable_breakpoint),
23115            (3, Breakpoint::new_standard()),
23116        ],
23117    );
23118}
23119
23120#[gpui::test]
23121async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23122    init_test(cx, |_| {});
23123    let capabilities = lsp::ServerCapabilities {
23124        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23125            prepare_provider: Some(true),
23126            work_done_progress_options: Default::default(),
23127        })),
23128        ..Default::default()
23129    };
23130    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23131
23132    cx.set_state(indoc! {"
23133        struct Fˇoo {}
23134    "});
23135
23136    cx.update_editor(|editor, _, cx| {
23137        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23138        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23139        editor.highlight_background::<DocumentHighlightRead>(
23140            &[highlight_range],
23141            |theme| theme.colors().editor_document_highlight_read_background,
23142            cx,
23143        );
23144    });
23145
23146    let mut prepare_rename_handler = cx
23147        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23148            move |_, _, _| async move {
23149                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23150                    start: lsp::Position {
23151                        line: 0,
23152                        character: 7,
23153                    },
23154                    end: lsp::Position {
23155                        line: 0,
23156                        character: 10,
23157                    },
23158                })))
23159            },
23160        );
23161    let prepare_rename_task = cx
23162        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23163        .expect("Prepare rename was not started");
23164    prepare_rename_handler.next().await.unwrap();
23165    prepare_rename_task.await.expect("Prepare rename failed");
23166
23167    let mut rename_handler =
23168        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23169            let edit = lsp::TextEdit {
23170                range: lsp::Range {
23171                    start: lsp::Position {
23172                        line: 0,
23173                        character: 7,
23174                    },
23175                    end: lsp::Position {
23176                        line: 0,
23177                        character: 10,
23178                    },
23179                },
23180                new_text: "FooRenamed".to_string(),
23181            };
23182            Ok(Some(lsp::WorkspaceEdit::new(
23183                // Specify the same edit twice
23184                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23185            )))
23186        });
23187    let rename_task = cx
23188        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23189        .expect("Confirm rename was not started");
23190    rename_handler.next().await.unwrap();
23191    rename_task.await.expect("Confirm rename failed");
23192    cx.run_until_parked();
23193
23194    // Despite two edits, only one is actually applied as those are identical
23195    cx.assert_editor_state(indoc! {"
23196        struct FooRenamedˇ {}
23197    "});
23198}
23199
23200#[gpui::test]
23201async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23202    init_test(cx, |_| {});
23203    // These capabilities indicate that the server does not support prepare rename.
23204    let capabilities = lsp::ServerCapabilities {
23205        rename_provider: Some(lsp::OneOf::Left(true)),
23206        ..Default::default()
23207    };
23208    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23209
23210    cx.set_state(indoc! {"
23211        struct Fˇoo {}
23212    "});
23213
23214    cx.update_editor(|editor, _window, cx| {
23215        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23216        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23217        editor.highlight_background::<DocumentHighlightRead>(
23218            &[highlight_range],
23219            |theme| theme.colors().editor_document_highlight_read_background,
23220            cx,
23221        );
23222    });
23223
23224    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23225        .expect("Prepare rename was not started")
23226        .await
23227        .expect("Prepare rename failed");
23228
23229    let mut rename_handler =
23230        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23231            let edit = lsp::TextEdit {
23232                range: lsp::Range {
23233                    start: lsp::Position {
23234                        line: 0,
23235                        character: 7,
23236                    },
23237                    end: lsp::Position {
23238                        line: 0,
23239                        character: 10,
23240                    },
23241                },
23242                new_text: "FooRenamed".to_string(),
23243            };
23244            Ok(Some(lsp::WorkspaceEdit::new(
23245                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23246            )))
23247        });
23248    let rename_task = cx
23249        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23250        .expect("Confirm rename was not started");
23251    rename_handler.next().await.unwrap();
23252    rename_task.await.expect("Confirm rename failed");
23253    cx.run_until_parked();
23254
23255    // Correct range is renamed, as `surrounding_word` is used to find it.
23256    cx.assert_editor_state(indoc! {"
23257        struct FooRenamedˇ {}
23258    "});
23259}
23260
23261#[gpui::test]
23262async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23263    init_test(cx, |_| {});
23264    let mut cx = EditorTestContext::new(cx).await;
23265
23266    let language = Arc::new(
23267        Language::new(
23268            LanguageConfig::default(),
23269            Some(tree_sitter_html::LANGUAGE.into()),
23270        )
23271        .with_brackets_query(
23272            r#"
23273            ("<" @open "/>" @close)
23274            ("</" @open ">" @close)
23275            ("<" @open ">" @close)
23276            ("\"" @open "\"" @close)
23277            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23278        "#,
23279        )
23280        .unwrap(),
23281    );
23282    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23283
23284    cx.set_state(indoc! {"
23285        <span>ˇ</span>
23286    "});
23287    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23288    cx.assert_editor_state(indoc! {"
23289        <span>
23290        ˇ
23291        </span>
23292    "});
23293
23294    cx.set_state(indoc! {"
23295        <span><span></span>ˇ</span>
23296    "});
23297    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23298    cx.assert_editor_state(indoc! {"
23299        <span><span></span>
23300        ˇ</span>
23301    "});
23302
23303    cx.set_state(indoc! {"
23304        <span>ˇ
23305        </span>
23306    "});
23307    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23308    cx.assert_editor_state(indoc! {"
23309        <span>
23310        ˇ
23311        </span>
23312    "});
23313}
23314
23315#[gpui::test(iterations = 10)]
23316async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23317    init_test(cx, |_| {});
23318
23319    let fs = FakeFs::new(cx.executor());
23320    fs.insert_tree(
23321        path!("/dir"),
23322        json!({
23323            "a.ts": "a",
23324        }),
23325    )
23326    .await;
23327
23328    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23329    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23330    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23331
23332    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23333    language_registry.add(Arc::new(Language::new(
23334        LanguageConfig {
23335            name: "TypeScript".into(),
23336            matcher: LanguageMatcher {
23337                path_suffixes: vec!["ts".to_string()],
23338                ..Default::default()
23339            },
23340            ..Default::default()
23341        },
23342        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23343    )));
23344    let mut fake_language_servers = language_registry.register_fake_lsp(
23345        "TypeScript",
23346        FakeLspAdapter {
23347            capabilities: lsp::ServerCapabilities {
23348                code_lens_provider: Some(lsp::CodeLensOptions {
23349                    resolve_provider: Some(true),
23350                }),
23351                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23352                    commands: vec!["_the/command".to_string()],
23353                    ..lsp::ExecuteCommandOptions::default()
23354                }),
23355                ..lsp::ServerCapabilities::default()
23356            },
23357            ..FakeLspAdapter::default()
23358        },
23359    );
23360
23361    let editor = workspace
23362        .update(cx, |workspace, window, cx| {
23363            workspace.open_abs_path(
23364                PathBuf::from(path!("/dir/a.ts")),
23365                OpenOptions::default(),
23366                window,
23367                cx,
23368            )
23369        })
23370        .unwrap()
23371        .await
23372        .unwrap()
23373        .downcast::<Editor>()
23374        .unwrap();
23375    cx.executor().run_until_parked();
23376
23377    let fake_server = fake_language_servers.next().await.unwrap();
23378
23379    let buffer = editor.update(cx, |editor, cx| {
23380        editor
23381            .buffer()
23382            .read(cx)
23383            .as_singleton()
23384            .expect("have opened a single file by path")
23385    });
23386
23387    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23388    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23389    drop(buffer_snapshot);
23390    let actions = cx
23391        .update_window(*workspace, |_, window, cx| {
23392            project.code_actions(&buffer, anchor..anchor, window, cx)
23393        })
23394        .unwrap();
23395
23396    fake_server
23397        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23398            Ok(Some(vec![
23399                lsp::CodeLens {
23400                    range: lsp::Range::default(),
23401                    command: Some(lsp::Command {
23402                        title: "Code lens command".to_owned(),
23403                        command: "_the/command".to_owned(),
23404                        arguments: None,
23405                    }),
23406                    data: None,
23407                },
23408                lsp::CodeLens {
23409                    range: lsp::Range::default(),
23410                    command: Some(lsp::Command {
23411                        title: "Command not in capabilities".to_owned(),
23412                        command: "not in capabilities".to_owned(),
23413                        arguments: None,
23414                    }),
23415                    data: None,
23416                },
23417                lsp::CodeLens {
23418                    range: lsp::Range {
23419                        start: lsp::Position {
23420                            line: 1,
23421                            character: 1,
23422                        },
23423                        end: lsp::Position {
23424                            line: 1,
23425                            character: 1,
23426                        },
23427                    },
23428                    command: Some(lsp::Command {
23429                        title: "Command not in range".to_owned(),
23430                        command: "_the/command".to_owned(),
23431                        arguments: None,
23432                    }),
23433                    data: None,
23434                },
23435            ]))
23436        })
23437        .next()
23438        .await;
23439
23440    let actions = actions.await.unwrap();
23441    assert_eq!(
23442        actions.len(),
23443        1,
23444        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23445    );
23446    let action = actions[0].clone();
23447    let apply = project.update(cx, |project, cx| {
23448        project.apply_code_action(buffer.clone(), action, true, cx)
23449    });
23450
23451    // Resolving the code action does not populate its edits. In absence of
23452    // edits, we must execute the given command.
23453    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23454        |mut lens, _| async move {
23455            let lens_command = lens.command.as_mut().expect("should have a command");
23456            assert_eq!(lens_command.title, "Code lens command");
23457            lens_command.arguments = Some(vec![json!("the-argument")]);
23458            Ok(lens)
23459        },
23460    );
23461
23462    // While executing the command, the language server sends the editor
23463    // a `workspaceEdit` request.
23464    fake_server
23465        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23466            let fake = fake_server.clone();
23467            move |params, _| {
23468                assert_eq!(params.command, "_the/command");
23469                let fake = fake.clone();
23470                async move {
23471                    fake.server
23472                        .request::<lsp::request::ApplyWorkspaceEdit>(
23473                            lsp::ApplyWorkspaceEditParams {
23474                                label: None,
23475                                edit: lsp::WorkspaceEdit {
23476                                    changes: Some(
23477                                        [(
23478                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23479                                            vec![lsp::TextEdit {
23480                                                range: lsp::Range::new(
23481                                                    lsp::Position::new(0, 0),
23482                                                    lsp::Position::new(0, 0),
23483                                                ),
23484                                                new_text: "X".into(),
23485                                            }],
23486                                        )]
23487                                        .into_iter()
23488                                        .collect(),
23489                                    ),
23490                                    ..lsp::WorkspaceEdit::default()
23491                                },
23492                            },
23493                        )
23494                        .await
23495                        .into_response()
23496                        .unwrap();
23497                    Ok(Some(json!(null)))
23498                }
23499            }
23500        })
23501        .next()
23502        .await;
23503
23504    // Applying the code lens command returns a project transaction containing the edits
23505    // sent by the language server in its `workspaceEdit` request.
23506    let transaction = apply.await.unwrap();
23507    assert!(transaction.0.contains_key(&buffer));
23508    buffer.update(cx, |buffer, cx| {
23509        assert_eq!(buffer.text(), "Xa");
23510        buffer.undo(cx);
23511        assert_eq!(buffer.text(), "a");
23512    });
23513
23514    let actions_after_edits = cx
23515        .update_window(*workspace, |_, window, cx| {
23516            project.code_actions(&buffer, anchor..anchor, window, cx)
23517        })
23518        .unwrap()
23519        .await
23520        .unwrap();
23521    assert_eq!(
23522        actions, actions_after_edits,
23523        "For the same selection, same code lens actions should be returned"
23524    );
23525
23526    let _responses =
23527        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23528            panic!("No more code lens requests are expected");
23529        });
23530    editor.update_in(cx, |editor, window, cx| {
23531        editor.select_all(&SelectAll, window, cx);
23532    });
23533    cx.executor().run_until_parked();
23534    let new_actions = cx
23535        .update_window(*workspace, |_, window, cx| {
23536            project.code_actions(&buffer, anchor..anchor, window, cx)
23537        })
23538        .unwrap()
23539        .await
23540        .unwrap();
23541    assert_eq!(
23542        actions, new_actions,
23543        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23544    );
23545}
23546
23547#[gpui::test]
23548async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23549    init_test(cx, |_| {});
23550
23551    let fs = FakeFs::new(cx.executor());
23552    let main_text = r#"fn main() {
23553println!("1");
23554println!("2");
23555println!("3");
23556println!("4");
23557println!("5");
23558}"#;
23559    let lib_text = "mod foo {}";
23560    fs.insert_tree(
23561        path!("/a"),
23562        json!({
23563            "lib.rs": lib_text,
23564            "main.rs": main_text,
23565        }),
23566    )
23567    .await;
23568
23569    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23570    let (workspace, cx) =
23571        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23572    let worktree_id = workspace.update(cx, |workspace, cx| {
23573        workspace.project().update(cx, |project, cx| {
23574            project.worktrees(cx).next().unwrap().read(cx).id()
23575        })
23576    });
23577
23578    let expected_ranges = vec![
23579        Point::new(0, 0)..Point::new(0, 0),
23580        Point::new(1, 0)..Point::new(1, 1),
23581        Point::new(2, 0)..Point::new(2, 2),
23582        Point::new(3, 0)..Point::new(3, 3),
23583    ];
23584
23585    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23586    let editor_1 = workspace
23587        .update_in(cx, |workspace, window, cx| {
23588            workspace.open_path(
23589                (worktree_id, rel_path("main.rs")),
23590                Some(pane_1.downgrade()),
23591                true,
23592                window,
23593                cx,
23594            )
23595        })
23596        .unwrap()
23597        .await
23598        .downcast::<Editor>()
23599        .unwrap();
23600    pane_1.update(cx, |pane, cx| {
23601        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23602        open_editor.update(cx, |editor, cx| {
23603            assert_eq!(
23604                editor.display_text(cx),
23605                main_text,
23606                "Original main.rs text on initial open",
23607            );
23608            assert_eq!(
23609                editor
23610                    .selections
23611                    .all::<Point>(cx)
23612                    .into_iter()
23613                    .map(|s| s.range())
23614                    .collect::<Vec<_>>(),
23615                vec![Point::zero()..Point::zero()],
23616                "Default selections on initial open",
23617            );
23618        })
23619    });
23620    editor_1.update_in(cx, |editor, window, cx| {
23621        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23622            s.select_ranges(expected_ranges.clone());
23623        });
23624    });
23625
23626    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23627        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23628    });
23629    let editor_2 = workspace
23630        .update_in(cx, |workspace, window, cx| {
23631            workspace.open_path(
23632                (worktree_id, rel_path("main.rs")),
23633                Some(pane_2.downgrade()),
23634                true,
23635                window,
23636                cx,
23637            )
23638        })
23639        .unwrap()
23640        .await
23641        .downcast::<Editor>()
23642        .unwrap();
23643    pane_2.update(cx, |pane, cx| {
23644        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23645        open_editor.update(cx, |editor, cx| {
23646            assert_eq!(
23647                editor.display_text(cx),
23648                main_text,
23649                "Original main.rs text on initial open in another panel",
23650            );
23651            assert_eq!(
23652                editor
23653                    .selections
23654                    .all::<Point>(cx)
23655                    .into_iter()
23656                    .map(|s| s.range())
23657                    .collect::<Vec<_>>(),
23658                vec![Point::zero()..Point::zero()],
23659                "Default selections on initial open in another panel",
23660            );
23661        })
23662    });
23663
23664    editor_2.update_in(cx, |editor, window, cx| {
23665        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23666    });
23667
23668    let _other_editor_1 = workspace
23669        .update_in(cx, |workspace, window, cx| {
23670            workspace.open_path(
23671                (worktree_id, rel_path("lib.rs")),
23672                Some(pane_1.downgrade()),
23673                true,
23674                window,
23675                cx,
23676            )
23677        })
23678        .unwrap()
23679        .await
23680        .downcast::<Editor>()
23681        .unwrap();
23682    pane_1
23683        .update_in(cx, |pane, window, cx| {
23684            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23685        })
23686        .await
23687        .unwrap();
23688    drop(editor_1);
23689    pane_1.update(cx, |pane, cx| {
23690        pane.active_item()
23691            .unwrap()
23692            .downcast::<Editor>()
23693            .unwrap()
23694            .update(cx, |editor, cx| {
23695                assert_eq!(
23696                    editor.display_text(cx),
23697                    lib_text,
23698                    "Other file should be open and active",
23699                );
23700            });
23701        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23702    });
23703
23704    let _other_editor_2 = workspace
23705        .update_in(cx, |workspace, window, cx| {
23706            workspace.open_path(
23707                (worktree_id, rel_path("lib.rs")),
23708                Some(pane_2.downgrade()),
23709                true,
23710                window,
23711                cx,
23712            )
23713        })
23714        .unwrap()
23715        .await
23716        .downcast::<Editor>()
23717        .unwrap();
23718    pane_2
23719        .update_in(cx, |pane, window, cx| {
23720            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23721        })
23722        .await
23723        .unwrap();
23724    drop(editor_2);
23725    pane_2.update(cx, |pane, cx| {
23726        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23727        open_editor.update(cx, |editor, cx| {
23728            assert_eq!(
23729                editor.display_text(cx),
23730                lib_text,
23731                "Other file should be open and active in another panel too",
23732            );
23733        });
23734        assert_eq!(
23735            pane.items().count(),
23736            1,
23737            "No other editors should be open in another pane",
23738        );
23739    });
23740
23741    let _editor_1_reopened = workspace
23742        .update_in(cx, |workspace, window, cx| {
23743            workspace.open_path(
23744                (worktree_id, rel_path("main.rs")),
23745                Some(pane_1.downgrade()),
23746                true,
23747                window,
23748                cx,
23749            )
23750        })
23751        .unwrap()
23752        .await
23753        .downcast::<Editor>()
23754        .unwrap();
23755    let _editor_2_reopened = workspace
23756        .update_in(cx, |workspace, window, cx| {
23757            workspace.open_path(
23758                (worktree_id, rel_path("main.rs")),
23759                Some(pane_2.downgrade()),
23760                true,
23761                window,
23762                cx,
23763            )
23764        })
23765        .unwrap()
23766        .await
23767        .downcast::<Editor>()
23768        .unwrap();
23769    pane_1.update(cx, |pane, cx| {
23770        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23771        open_editor.update(cx, |editor, cx| {
23772            assert_eq!(
23773                editor.display_text(cx),
23774                main_text,
23775                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23776            );
23777            assert_eq!(
23778                editor
23779                    .selections
23780                    .all::<Point>(cx)
23781                    .into_iter()
23782                    .map(|s| s.range())
23783                    .collect::<Vec<_>>(),
23784                expected_ranges,
23785                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23786            );
23787        })
23788    });
23789    pane_2.update(cx, |pane, cx| {
23790        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23791        open_editor.update(cx, |editor, cx| {
23792            assert_eq!(
23793                editor.display_text(cx),
23794                r#"fn main() {
23795⋯rintln!("1");
23796⋯intln!("2");
23797⋯ntln!("3");
23798println!("4");
23799println!("5");
23800}"#,
23801                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23802            );
23803            assert_eq!(
23804                editor
23805                    .selections
23806                    .all::<Point>(cx)
23807                    .into_iter()
23808                    .map(|s| s.range())
23809                    .collect::<Vec<_>>(),
23810                vec![Point::zero()..Point::zero()],
23811                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23812            );
23813        })
23814    });
23815}
23816
23817#[gpui::test]
23818async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23819    init_test(cx, |_| {});
23820
23821    let fs = FakeFs::new(cx.executor());
23822    let main_text = r#"fn main() {
23823println!("1");
23824println!("2");
23825println!("3");
23826println!("4");
23827println!("5");
23828}"#;
23829    let lib_text = "mod foo {}";
23830    fs.insert_tree(
23831        path!("/a"),
23832        json!({
23833            "lib.rs": lib_text,
23834            "main.rs": main_text,
23835        }),
23836    )
23837    .await;
23838
23839    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23840    let (workspace, cx) =
23841        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23842    let worktree_id = workspace.update(cx, |workspace, cx| {
23843        workspace.project().update(cx, |project, cx| {
23844            project.worktrees(cx).next().unwrap().read(cx).id()
23845        })
23846    });
23847
23848    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23849    let editor = workspace
23850        .update_in(cx, |workspace, window, cx| {
23851            workspace.open_path(
23852                (worktree_id, rel_path("main.rs")),
23853                Some(pane.downgrade()),
23854                true,
23855                window,
23856                cx,
23857            )
23858        })
23859        .unwrap()
23860        .await
23861        .downcast::<Editor>()
23862        .unwrap();
23863    pane.update(cx, |pane, cx| {
23864        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23865        open_editor.update(cx, |editor, cx| {
23866            assert_eq!(
23867                editor.display_text(cx),
23868                main_text,
23869                "Original main.rs text on initial open",
23870            );
23871        })
23872    });
23873    editor.update_in(cx, |editor, window, cx| {
23874        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23875    });
23876
23877    cx.update_global(|store: &mut SettingsStore, cx| {
23878        store.update_user_settings(cx, |s| {
23879            s.workspace.restore_on_file_reopen = Some(false);
23880        });
23881    });
23882    editor.update_in(cx, |editor, window, cx| {
23883        editor.fold_ranges(
23884            vec![
23885                Point::new(1, 0)..Point::new(1, 1),
23886                Point::new(2, 0)..Point::new(2, 2),
23887                Point::new(3, 0)..Point::new(3, 3),
23888            ],
23889            false,
23890            window,
23891            cx,
23892        );
23893    });
23894    pane.update_in(cx, |pane, window, cx| {
23895        pane.close_all_items(&CloseAllItems::default(), window, cx)
23896    })
23897    .await
23898    .unwrap();
23899    pane.update(cx, |pane, _| {
23900        assert!(pane.active_item().is_none());
23901    });
23902    cx.update_global(|store: &mut SettingsStore, cx| {
23903        store.update_user_settings(cx, |s| {
23904            s.workspace.restore_on_file_reopen = Some(true);
23905        });
23906    });
23907
23908    let _editor_reopened = workspace
23909        .update_in(cx, |workspace, window, cx| {
23910            workspace.open_path(
23911                (worktree_id, rel_path("main.rs")),
23912                Some(pane.downgrade()),
23913                true,
23914                window,
23915                cx,
23916            )
23917        })
23918        .unwrap()
23919        .await
23920        .downcast::<Editor>()
23921        .unwrap();
23922    pane.update(cx, |pane, cx| {
23923        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23924        open_editor.update(cx, |editor, cx| {
23925            assert_eq!(
23926                editor.display_text(cx),
23927                main_text,
23928                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23929            );
23930        })
23931    });
23932}
23933
23934#[gpui::test]
23935async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23936    struct EmptyModalView {
23937        focus_handle: gpui::FocusHandle,
23938    }
23939    impl EventEmitter<DismissEvent> for EmptyModalView {}
23940    impl Render for EmptyModalView {
23941        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23942            div()
23943        }
23944    }
23945    impl Focusable for EmptyModalView {
23946        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23947            self.focus_handle.clone()
23948        }
23949    }
23950    impl workspace::ModalView for EmptyModalView {}
23951    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23952        EmptyModalView {
23953            focus_handle: cx.focus_handle(),
23954        }
23955    }
23956
23957    init_test(cx, |_| {});
23958
23959    let fs = FakeFs::new(cx.executor());
23960    let project = Project::test(fs, [], cx).await;
23961    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23962    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23963    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23964    let editor = cx.new_window_entity(|window, cx| {
23965        Editor::new(
23966            EditorMode::full(),
23967            buffer,
23968            Some(project.clone()),
23969            window,
23970            cx,
23971        )
23972    });
23973    workspace
23974        .update(cx, |workspace, window, cx| {
23975            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23976        })
23977        .unwrap();
23978    editor.update_in(cx, |editor, window, cx| {
23979        editor.open_context_menu(&OpenContextMenu, window, cx);
23980        assert!(editor.mouse_context_menu.is_some());
23981    });
23982    workspace
23983        .update(cx, |workspace, window, cx| {
23984            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23985        })
23986        .unwrap();
23987    cx.read(|cx| {
23988        assert!(editor.read(cx).mouse_context_menu.is_none());
23989    });
23990}
23991
23992fn set_linked_edit_ranges(
23993    opening: (Point, Point),
23994    closing: (Point, Point),
23995    editor: &mut Editor,
23996    cx: &mut Context<Editor>,
23997) {
23998    let Some((buffer, _)) = editor
23999        .buffer
24000        .read(cx)
24001        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24002    else {
24003        panic!("Failed to get buffer for selection position");
24004    };
24005    let buffer = buffer.read(cx);
24006    let buffer_id = buffer.remote_id();
24007    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24008    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24009    let mut linked_ranges = HashMap::default();
24010    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24011    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24012}
24013
24014#[gpui::test]
24015async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24016    init_test(cx, |_| {});
24017
24018    let fs = FakeFs::new(cx.executor());
24019    fs.insert_file(path!("/file.html"), Default::default())
24020        .await;
24021
24022    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24023
24024    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24025    let html_language = Arc::new(Language::new(
24026        LanguageConfig {
24027            name: "HTML".into(),
24028            matcher: LanguageMatcher {
24029                path_suffixes: vec!["html".to_string()],
24030                ..LanguageMatcher::default()
24031            },
24032            brackets: BracketPairConfig {
24033                pairs: vec![BracketPair {
24034                    start: "<".into(),
24035                    end: ">".into(),
24036                    close: true,
24037                    ..Default::default()
24038                }],
24039                ..Default::default()
24040            },
24041            ..Default::default()
24042        },
24043        Some(tree_sitter_html::LANGUAGE.into()),
24044    ));
24045    language_registry.add(html_language);
24046    let mut fake_servers = language_registry.register_fake_lsp(
24047        "HTML",
24048        FakeLspAdapter {
24049            capabilities: lsp::ServerCapabilities {
24050                completion_provider: Some(lsp::CompletionOptions {
24051                    resolve_provider: Some(true),
24052                    ..Default::default()
24053                }),
24054                ..Default::default()
24055            },
24056            ..Default::default()
24057        },
24058    );
24059
24060    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24061    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24062
24063    let worktree_id = workspace
24064        .update(cx, |workspace, _window, cx| {
24065            workspace.project().update(cx, |project, cx| {
24066                project.worktrees(cx).next().unwrap().read(cx).id()
24067            })
24068        })
24069        .unwrap();
24070    project
24071        .update(cx, |project, cx| {
24072            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24073        })
24074        .await
24075        .unwrap();
24076    let editor = workspace
24077        .update(cx, |workspace, window, cx| {
24078            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24079        })
24080        .unwrap()
24081        .await
24082        .unwrap()
24083        .downcast::<Editor>()
24084        .unwrap();
24085
24086    let fake_server = fake_servers.next().await.unwrap();
24087    editor.update_in(cx, |editor, window, cx| {
24088        editor.set_text("<ad></ad>", window, cx);
24089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24090            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24091        });
24092        set_linked_edit_ranges(
24093            (Point::new(0, 1), Point::new(0, 3)),
24094            (Point::new(0, 6), Point::new(0, 8)),
24095            editor,
24096            cx,
24097        );
24098    });
24099    let mut completion_handle =
24100        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24101            Ok(Some(lsp::CompletionResponse::Array(vec![
24102                lsp::CompletionItem {
24103                    label: "head".to_string(),
24104                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24105                        lsp::InsertReplaceEdit {
24106                            new_text: "head".to_string(),
24107                            insert: lsp::Range::new(
24108                                lsp::Position::new(0, 1),
24109                                lsp::Position::new(0, 3),
24110                            ),
24111                            replace: lsp::Range::new(
24112                                lsp::Position::new(0, 1),
24113                                lsp::Position::new(0, 3),
24114                            ),
24115                        },
24116                    )),
24117                    ..Default::default()
24118                },
24119            ])))
24120        });
24121    editor.update_in(cx, |editor, window, cx| {
24122        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24123    });
24124    cx.run_until_parked();
24125    completion_handle.next().await.unwrap();
24126    editor.update(cx, |editor, _| {
24127        assert!(
24128            editor.context_menu_visible(),
24129            "Completion menu should be visible"
24130        );
24131    });
24132    editor.update_in(cx, |editor, window, cx| {
24133        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24134    });
24135    cx.executor().run_until_parked();
24136    editor.update(cx, |editor, cx| {
24137        assert_eq!(editor.text(cx), "<head></head>");
24138    });
24139}
24140
24141#[gpui::test]
24142async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24143    init_test(cx, |_| {});
24144
24145    let mut cx = EditorTestContext::new(cx).await;
24146    let language = Arc::new(Language::new(
24147        LanguageConfig {
24148            name: "TSX".into(),
24149            matcher: LanguageMatcher {
24150                path_suffixes: vec!["tsx".to_string()],
24151                ..LanguageMatcher::default()
24152            },
24153            brackets: BracketPairConfig {
24154                pairs: vec![BracketPair {
24155                    start: "<".into(),
24156                    end: ">".into(),
24157                    close: true,
24158                    ..Default::default()
24159                }],
24160                ..Default::default()
24161            },
24162            linked_edit_characters: HashSet::from_iter(['.']),
24163            ..Default::default()
24164        },
24165        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24166    ));
24167    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24168
24169    // Test typing > does not extend linked pair
24170    cx.set_state("<divˇ<div></div>");
24171    cx.update_editor(|editor, _, cx| {
24172        set_linked_edit_ranges(
24173            (Point::new(0, 1), Point::new(0, 4)),
24174            (Point::new(0, 11), Point::new(0, 14)),
24175            editor,
24176            cx,
24177        );
24178    });
24179    cx.update_editor(|editor, window, cx| {
24180        editor.handle_input(">", window, cx);
24181    });
24182    cx.assert_editor_state("<div>ˇ<div></div>");
24183
24184    // Test typing . do extend linked pair
24185    cx.set_state("<Animatedˇ></Animated>");
24186    cx.update_editor(|editor, _, cx| {
24187        set_linked_edit_ranges(
24188            (Point::new(0, 1), Point::new(0, 9)),
24189            (Point::new(0, 12), Point::new(0, 20)),
24190            editor,
24191            cx,
24192        );
24193    });
24194    cx.update_editor(|editor, window, cx| {
24195        editor.handle_input(".", window, cx);
24196    });
24197    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24198    cx.update_editor(|editor, _, cx| {
24199        set_linked_edit_ranges(
24200            (Point::new(0, 1), Point::new(0, 10)),
24201            (Point::new(0, 13), Point::new(0, 21)),
24202            editor,
24203            cx,
24204        );
24205    });
24206    cx.update_editor(|editor, window, cx| {
24207        editor.handle_input("V", window, cx);
24208    });
24209    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24210}
24211
24212#[gpui::test]
24213async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24214    init_test(cx, |_| {});
24215
24216    let fs = FakeFs::new(cx.executor());
24217    fs.insert_tree(
24218        path!("/root"),
24219        json!({
24220            "a": {
24221                "main.rs": "fn main() {}",
24222            },
24223            "foo": {
24224                "bar": {
24225                    "external_file.rs": "pub mod external {}",
24226                }
24227            }
24228        }),
24229    )
24230    .await;
24231
24232    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24233    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24234    language_registry.add(rust_lang());
24235    let _fake_servers = language_registry.register_fake_lsp(
24236        "Rust",
24237        FakeLspAdapter {
24238            ..FakeLspAdapter::default()
24239        },
24240    );
24241    let (workspace, cx) =
24242        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24243    let worktree_id = workspace.update(cx, |workspace, cx| {
24244        workspace.project().update(cx, |project, cx| {
24245            project.worktrees(cx).next().unwrap().read(cx).id()
24246        })
24247    });
24248
24249    let assert_language_servers_count =
24250        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24251            project.update(cx, |project, cx| {
24252                let current = project
24253                    .lsp_store()
24254                    .read(cx)
24255                    .as_local()
24256                    .unwrap()
24257                    .language_servers
24258                    .len();
24259                assert_eq!(expected, current, "{context}");
24260            });
24261        };
24262
24263    assert_language_servers_count(
24264        0,
24265        "No servers should be running before any file is open",
24266        cx,
24267    );
24268    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24269    let main_editor = workspace
24270        .update_in(cx, |workspace, window, cx| {
24271            workspace.open_path(
24272                (worktree_id, rel_path("main.rs")),
24273                Some(pane.downgrade()),
24274                true,
24275                window,
24276                cx,
24277            )
24278        })
24279        .unwrap()
24280        .await
24281        .downcast::<Editor>()
24282        .unwrap();
24283    pane.update(cx, |pane, cx| {
24284        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24285        open_editor.update(cx, |editor, cx| {
24286            assert_eq!(
24287                editor.display_text(cx),
24288                "fn main() {}",
24289                "Original main.rs text on initial open",
24290            );
24291        });
24292        assert_eq!(open_editor, main_editor);
24293    });
24294    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24295
24296    let external_editor = workspace
24297        .update_in(cx, |workspace, window, cx| {
24298            workspace.open_abs_path(
24299                PathBuf::from("/root/foo/bar/external_file.rs"),
24300                OpenOptions::default(),
24301                window,
24302                cx,
24303            )
24304        })
24305        .await
24306        .expect("opening external file")
24307        .downcast::<Editor>()
24308        .expect("downcasted external file's open element to editor");
24309    pane.update(cx, |pane, cx| {
24310        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24311        open_editor.update(cx, |editor, cx| {
24312            assert_eq!(
24313                editor.display_text(cx),
24314                "pub mod external {}",
24315                "External file is open now",
24316            );
24317        });
24318        assert_eq!(open_editor, external_editor);
24319    });
24320    assert_language_servers_count(
24321        1,
24322        "Second, external, *.rs file should join the existing server",
24323        cx,
24324    );
24325
24326    pane.update_in(cx, |pane, window, cx| {
24327        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24328    })
24329    .await
24330    .unwrap();
24331    pane.update_in(cx, |pane, window, cx| {
24332        pane.navigate_backward(&Default::default(), window, cx);
24333    });
24334    cx.run_until_parked();
24335    pane.update(cx, |pane, cx| {
24336        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24337        open_editor.update(cx, |editor, cx| {
24338            assert_eq!(
24339                editor.display_text(cx),
24340                "pub mod external {}",
24341                "External file is open now",
24342            );
24343        });
24344    });
24345    assert_language_servers_count(
24346        1,
24347        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24348        cx,
24349    );
24350
24351    cx.update(|_, cx| {
24352        workspace::reload(cx);
24353    });
24354    assert_language_servers_count(
24355        1,
24356        "After reloading the worktree with local and external files opened, only one project should be started",
24357        cx,
24358    );
24359}
24360
24361#[gpui::test]
24362async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24363    init_test(cx, |_| {});
24364
24365    let mut cx = EditorTestContext::new(cx).await;
24366    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24367    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24368
24369    // test cursor move to start of each line on tab
24370    // for `if`, `elif`, `else`, `while`, `with` and `for`
24371    cx.set_state(indoc! {"
24372        def main():
24373        ˇ    for item in items:
24374        ˇ        while item.active:
24375        ˇ            if item.value > 10:
24376        ˇ                continue
24377        ˇ            elif item.value < 0:
24378        ˇ                break
24379        ˇ            else:
24380        ˇ                with item.context() as ctx:
24381        ˇ                    yield count
24382        ˇ        else:
24383        ˇ            log('while else')
24384        ˇ    else:
24385        ˇ        log('for else')
24386    "});
24387    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24388    cx.assert_editor_state(indoc! {"
24389        def main():
24390            ˇfor item in items:
24391                ˇwhile item.active:
24392                    ˇif item.value > 10:
24393                        ˇcontinue
24394                    ˇelif item.value < 0:
24395                        ˇbreak
24396                    ˇelse:
24397                        ˇwith item.context() as ctx:
24398                            ˇyield count
24399                ˇelse:
24400                    ˇlog('while else')
24401            ˇelse:
24402                ˇlog('for else')
24403    "});
24404    // test relative indent is preserved when tab
24405    // for `if`, `elif`, `else`, `while`, `with` and `for`
24406    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24407    cx.assert_editor_state(indoc! {"
24408        def main():
24409                ˇfor item in items:
24410                    ˇwhile item.active:
24411                        ˇif item.value > 10:
24412                            ˇcontinue
24413                        ˇelif item.value < 0:
24414                            ˇbreak
24415                        ˇelse:
24416                            ˇwith item.context() as ctx:
24417                                ˇyield count
24418                    ˇelse:
24419                        ˇlog('while else')
24420                ˇelse:
24421                    ˇlog('for else')
24422    "});
24423
24424    // test cursor move to start of each line on tab
24425    // for `try`, `except`, `else`, `finally`, `match` and `def`
24426    cx.set_state(indoc! {"
24427        def main():
24428        ˇ    try:
24429        ˇ        fetch()
24430        ˇ    except ValueError:
24431        ˇ        handle_error()
24432        ˇ    else:
24433        ˇ        match value:
24434        ˇ            case _:
24435        ˇ    finally:
24436        ˇ        def status():
24437        ˇ            return 0
24438    "});
24439    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24440    cx.assert_editor_state(indoc! {"
24441        def main():
24442            ˇtry:
24443                ˇfetch()
24444            ˇexcept ValueError:
24445                ˇhandle_error()
24446            ˇelse:
24447                ˇmatch value:
24448                    ˇcase _:
24449            ˇfinally:
24450                ˇdef status():
24451                    ˇreturn 0
24452    "});
24453    // test relative indent is preserved when tab
24454    // for `try`, `except`, `else`, `finally`, `match` and `def`
24455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24456    cx.assert_editor_state(indoc! {"
24457        def main():
24458                ˇtry:
24459                    ˇfetch()
24460                ˇexcept ValueError:
24461                    ˇhandle_error()
24462                ˇelse:
24463                    ˇmatch value:
24464                        ˇcase _:
24465                ˇfinally:
24466                    ˇdef status():
24467                        ˇreturn 0
24468    "});
24469}
24470
24471#[gpui::test]
24472async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24473    init_test(cx, |_| {});
24474
24475    let mut cx = EditorTestContext::new(cx).await;
24476    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24477    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24478
24479    // test `else` auto outdents when typed inside `if` block
24480    cx.set_state(indoc! {"
24481        def main():
24482            if i == 2:
24483                return
24484                ˇ
24485    "});
24486    cx.update_editor(|editor, window, cx| {
24487        editor.handle_input("else:", window, cx);
24488    });
24489    cx.assert_editor_state(indoc! {"
24490        def main():
24491            if i == 2:
24492                return
24493            else:ˇ
24494    "});
24495
24496    // test `except` auto outdents when typed inside `try` block
24497    cx.set_state(indoc! {"
24498        def main():
24499            try:
24500                i = 2
24501                ˇ
24502    "});
24503    cx.update_editor(|editor, window, cx| {
24504        editor.handle_input("except:", window, cx);
24505    });
24506    cx.assert_editor_state(indoc! {"
24507        def main():
24508            try:
24509                i = 2
24510            except:ˇ
24511    "});
24512
24513    // test `else` auto outdents when typed inside `except` block
24514    cx.set_state(indoc! {"
24515        def main():
24516            try:
24517                i = 2
24518            except:
24519                j = 2
24520                ˇ
24521    "});
24522    cx.update_editor(|editor, window, cx| {
24523        editor.handle_input("else:", window, cx);
24524    });
24525    cx.assert_editor_state(indoc! {"
24526        def main():
24527            try:
24528                i = 2
24529            except:
24530                j = 2
24531            else:ˇ
24532    "});
24533
24534    // test `finally` auto outdents when typed inside `else` block
24535    cx.set_state(indoc! {"
24536        def main():
24537            try:
24538                i = 2
24539            except:
24540                j = 2
24541            else:
24542                k = 2
24543                ˇ
24544    "});
24545    cx.update_editor(|editor, window, cx| {
24546        editor.handle_input("finally:", window, cx);
24547    });
24548    cx.assert_editor_state(indoc! {"
24549        def main():
24550            try:
24551                i = 2
24552            except:
24553                j = 2
24554            else:
24555                k = 2
24556            finally:ˇ
24557    "});
24558
24559    // test `else` does not outdents when typed inside `except` block right after for block
24560    cx.set_state(indoc! {"
24561        def main():
24562            try:
24563                i = 2
24564            except:
24565                for i in range(n):
24566                    pass
24567                ˇ
24568    "});
24569    cx.update_editor(|editor, window, cx| {
24570        editor.handle_input("else:", window, cx);
24571    });
24572    cx.assert_editor_state(indoc! {"
24573        def main():
24574            try:
24575                i = 2
24576            except:
24577                for i in range(n):
24578                    pass
24579                else:ˇ
24580    "});
24581
24582    // test `finally` auto outdents when typed inside `else` block right after for block
24583    cx.set_state(indoc! {"
24584        def main():
24585            try:
24586                i = 2
24587            except:
24588                j = 2
24589            else:
24590                for i in range(n):
24591                    pass
24592                ˇ
24593    "});
24594    cx.update_editor(|editor, window, cx| {
24595        editor.handle_input("finally:", window, cx);
24596    });
24597    cx.assert_editor_state(indoc! {"
24598        def main():
24599            try:
24600                i = 2
24601            except:
24602                j = 2
24603            else:
24604                for i in range(n):
24605                    pass
24606            finally:ˇ
24607    "});
24608
24609    // test `except` outdents to inner "try" block
24610    cx.set_state(indoc! {"
24611        def main():
24612            try:
24613                i = 2
24614                if i == 2:
24615                    try:
24616                        i = 3
24617                        ˇ
24618    "});
24619    cx.update_editor(|editor, window, cx| {
24620        editor.handle_input("except:", window, cx);
24621    });
24622    cx.assert_editor_state(indoc! {"
24623        def main():
24624            try:
24625                i = 2
24626                if i == 2:
24627                    try:
24628                        i = 3
24629                    except:ˇ
24630    "});
24631
24632    // test `except` outdents to outer "try" block
24633    cx.set_state(indoc! {"
24634        def main():
24635            try:
24636                i = 2
24637                if i == 2:
24638                    try:
24639                        i = 3
24640                ˇ
24641    "});
24642    cx.update_editor(|editor, window, cx| {
24643        editor.handle_input("except:", window, cx);
24644    });
24645    cx.assert_editor_state(indoc! {"
24646        def main():
24647            try:
24648                i = 2
24649                if i == 2:
24650                    try:
24651                        i = 3
24652            except:ˇ
24653    "});
24654
24655    // test `else` stays at correct indent when typed after `for` block
24656    cx.set_state(indoc! {"
24657        def main():
24658            for i in range(10):
24659                if i == 3:
24660                    break
24661            ˇ
24662    "});
24663    cx.update_editor(|editor, window, cx| {
24664        editor.handle_input("else:", window, cx);
24665    });
24666    cx.assert_editor_state(indoc! {"
24667        def main():
24668            for i in range(10):
24669                if i == 3:
24670                    break
24671            else:ˇ
24672    "});
24673
24674    // test does not outdent on typing after line with square brackets
24675    cx.set_state(indoc! {"
24676        def f() -> list[str]:
24677            ˇ
24678    "});
24679    cx.update_editor(|editor, window, cx| {
24680        editor.handle_input("a", window, cx);
24681    });
24682    cx.assert_editor_state(indoc! {"
24683        def f() -> list[str]:
2468424685    "});
24686
24687    // test does not outdent on typing : after case keyword
24688    cx.set_state(indoc! {"
24689        match 1:
24690            caseˇ
24691    "});
24692    cx.update_editor(|editor, window, cx| {
24693        editor.handle_input(":", window, cx);
24694    });
24695    cx.assert_editor_state(indoc! {"
24696        match 1:
24697            case:ˇ
24698    "});
24699}
24700
24701#[gpui::test]
24702async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24703    init_test(cx, |_| {});
24704    update_test_language_settings(cx, |settings| {
24705        settings.defaults.extend_comment_on_newline = Some(false);
24706    });
24707    let mut cx = EditorTestContext::new(cx).await;
24708    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24709    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24710
24711    // test correct indent after newline on comment
24712    cx.set_state(indoc! {"
24713        # COMMENT:ˇ
24714    "});
24715    cx.update_editor(|editor, window, cx| {
24716        editor.newline(&Newline, window, cx);
24717    });
24718    cx.assert_editor_state(indoc! {"
24719        # COMMENT:
24720        ˇ
24721    "});
24722
24723    // test correct indent after newline in brackets
24724    cx.set_state(indoc! {"
24725        {ˇ}
24726    "});
24727    cx.update_editor(|editor, window, cx| {
24728        editor.newline(&Newline, window, cx);
24729    });
24730    cx.run_until_parked();
24731    cx.assert_editor_state(indoc! {"
24732        {
24733            ˇ
24734        }
24735    "});
24736
24737    cx.set_state(indoc! {"
24738        (ˇ)
24739    "});
24740    cx.update_editor(|editor, window, cx| {
24741        editor.newline(&Newline, window, cx);
24742    });
24743    cx.run_until_parked();
24744    cx.assert_editor_state(indoc! {"
24745        (
24746            ˇ
24747        )
24748    "});
24749
24750    // do not indent after empty lists or dictionaries
24751    cx.set_state(indoc! {"
24752        a = []ˇ
24753    "});
24754    cx.update_editor(|editor, window, cx| {
24755        editor.newline(&Newline, window, cx);
24756    });
24757    cx.run_until_parked();
24758    cx.assert_editor_state(indoc! {"
24759        a = []
24760        ˇ
24761    "});
24762}
24763
24764#[gpui::test]
24765async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24766    init_test(cx, |_| {});
24767
24768    let mut cx = EditorTestContext::new(cx).await;
24769    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24770    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24771
24772    // test cursor move to start of each line on tab
24773    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24774    cx.set_state(indoc! {"
24775        function main() {
24776        ˇ    for item in $items; do
24777        ˇ        while [ -n \"$item\" ]; do
24778        ˇ            if [ \"$value\" -gt 10 ]; then
24779        ˇ                continue
24780        ˇ            elif [ \"$value\" -lt 0 ]; then
24781        ˇ                break
24782        ˇ            else
24783        ˇ                echo \"$item\"
24784        ˇ            fi
24785        ˇ        done
24786        ˇ    done
24787        ˇ}
24788    "});
24789    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24790    cx.assert_editor_state(indoc! {"
24791        function main() {
24792            ˇfor item in $items; do
24793                ˇwhile [ -n \"$item\" ]; do
24794                    ˇif [ \"$value\" -gt 10 ]; then
24795                        ˇcontinue
24796                    ˇelif [ \"$value\" -lt 0 ]; then
24797                        ˇbreak
24798                    ˇelse
24799                        ˇecho \"$item\"
24800                    ˇfi
24801                ˇdone
24802            ˇdone
24803        ˇ}
24804    "});
24805    // test relative indent is preserved when tab
24806    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24807    cx.assert_editor_state(indoc! {"
24808        function main() {
24809                ˇfor item in $items; do
24810                    ˇwhile [ -n \"$item\" ]; do
24811                        ˇif [ \"$value\" -gt 10 ]; then
24812                            ˇcontinue
24813                        ˇelif [ \"$value\" -lt 0 ]; then
24814                            ˇbreak
24815                        ˇelse
24816                            ˇecho \"$item\"
24817                        ˇfi
24818                    ˇdone
24819                ˇdone
24820            ˇ}
24821    "});
24822
24823    // test cursor move to start of each line on tab
24824    // for `case` statement with patterns
24825    cx.set_state(indoc! {"
24826        function handle() {
24827        ˇ    case \"$1\" in
24828        ˇ        start)
24829        ˇ            echo \"a\"
24830        ˇ            ;;
24831        ˇ        stop)
24832        ˇ            echo \"b\"
24833        ˇ            ;;
24834        ˇ        *)
24835        ˇ            echo \"c\"
24836        ˇ            ;;
24837        ˇ    esac
24838        ˇ}
24839    "});
24840    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24841    cx.assert_editor_state(indoc! {"
24842        function handle() {
24843            ˇcase \"$1\" in
24844                ˇstart)
24845                    ˇecho \"a\"
24846                    ˇ;;
24847                ˇstop)
24848                    ˇecho \"b\"
24849                    ˇ;;
24850                ˇ*)
24851                    ˇecho \"c\"
24852                    ˇ;;
24853            ˇesac
24854        ˇ}
24855    "});
24856}
24857
24858#[gpui::test]
24859async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24860    init_test(cx, |_| {});
24861
24862    let mut cx = EditorTestContext::new(cx).await;
24863    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24864    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24865
24866    // test indents on comment insert
24867    cx.set_state(indoc! {"
24868        function main() {
24869        ˇ    for item in $items; do
24870        ˇ        while [ -n \"$item\" ]; do
24871        ˇ            if [ \"$value\" -gt 10 ]; then
24872        ˇ                continue
24873        ˇ            elif [ \"$value\" -lt 0 ]; then
24874        ˇ                break
24875        ˇ            else
24876        ˇ                echo \"$item\"
24877        ˇ            fi
24878        ˇ        done
24879        ˇ    done
24880        ˇ}
24881    "});
24882    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24883    cx.assert_editor_state(indoc! {"
24884        function main() {
24885        #ˇ    for item in $items; do
24886        #ˇ        while [ -n \"$item\" ]; do
24887        #ˇ            if [ \"$value\" -gt 10 ]; then
24888        #ˇ                continue
24889        #ˇ            elif [ \"$value\" -lt 0 ]; then
24890        #ˇ                break
24891        #ˇ            else
24892        #ˇ                echo \"$item\"
24893        #ˇ            fi
24894        #ˇ        done
24895        #ˇ    done
24896        #ˇ}
24897    "});
24898}
24899
24900#[gpui::test]
24901async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24902    init_test(cx, |_| {});
24903
24904    let mut cx = EditorTestContext::new(cx).await;
24905    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24906    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24907
24908    // test `else` auto outdents when typed inside `if` block
24909    cx.set_state(indoc! {"
24910        if [ \"$1\" = \"test\" ]; then
24911            echo \"foo bar\"
24912            ˇ
24913    "});
24914    cx.update_editor(|editor, window, cx| {
24915        editor.handle_input("else", window, cx);
24916    });
24917    cx.assert_editor_state(indoc! {"
24918        if [ \"$1\" = \"test\" ]; then
24919            echo \"foo bar\"
24920        elseˇ
24921    "});
24922
24923    // test `elif` auto outdents when typed inside `if` block
24924    cx.set_state(indoc! {"
24925        if [ \"$1\" = \"test\" ]; then
24926            echo \"foo bar\"
24927            ˇ
24928    "});
24929    cx.update_editor(|editor, window, cx| {
24930        editor.handle_input("elif", window, cx);
24931    });
24932    cx.assert_editor_state(indoc! {"
24933        if [ \"$1\" = \"test\" ]; then
24934            echo \"foo bar\"
24935        elifˇ
24936    "});
24937
24938    // test `fi` auto outdents when typed inside `else` block
24939    cx.set_state(indoc! {"
24940        if [ \"$1\" = \"test\" ]; then
24941            echo \"foo bar\"
24942        else
24943            echo \"bar baz\"
24944            ˇ
24945    "});
24946    cx.update_editor(|editor, window, cx| {
24947        editor.handle_input("fi", window, cx);
24948    });
24949    cx.assert_editor_state(indoc! {"
24950        if [ \"$1\" = \"test\" ]; then
24951            echo \"foo bar\"
24952        else
24953            echo \"bar baz\"
24954        fiˇ
24955    "});
24956
24957    // test `done` auto outdents when typed inside `while` block
24958    cx.set_state(indoc! {"
24959        while read line; do
24960            echo \"$line\"
24961            ˇ
24962    "});
24963    cx.update_editor(|editor, window, cx| {
24964        editor.handle_input("done", window, cx);
24965    });
24966    cx.assert_editor_state(indoc! {"
24967        while read line; do
24968            echo \"$line\"
24969        doneˇ
24970    "});
24971
24972    // test `done` auto outdents when typed inside `for` block
24973    cx.set_state(indoc! {"
24974        for file in *.txt; do
24975            cat \"$file\"
24976            ˇ
24977    "});
24978    cx.update_editor(|editor, window, cx| {
24979        editor.handle_input("done", window, cx);
24980    });
24981    cx.assert_editor_state(indoc! {"
24982        for file in *.txt; do
24983            cat \"$file\"
24984        doneˇ
24985    "});
24986
24987    // test `esac` auto outdents when typed inside `case` block
24988    cx.set_state(indoc! {"
24989        case \"$1\" in
24990            start)
24991                echo \"foo bar\"
24992                ;;
24993            stop)
24994                echo \"bar baz\"
24995                ;;
24996            ˇ
24997    "});
24998    cx.update_editor(|editor, window, cx| {
24999        editor.handle_input("esac", window, cx);
25000    });
25001    cx.assert_editor_state(indoc! {"
25002        case \"$1\" in
25003            start)
25004                echo \"foo bar\"
25005                ;;
25006            stop)
25007                echo \"bar baz\"
25008                ;;
25009        esacˇ
25010    "});
25011
25012    // test `*)` auto outdents when typed inside `case` block
25013    cx.set_state(indoc! {"
25014        case \"$1\" in
25015            start)
25016                echo \"foo bar\"
25017                ;;
25018                ˇ
25019    "});
25020    cx.update_editor(|editor, window, cx| {
25021        editor.handle_input("*)", window, cx);
25022    });
25023    cx.assert_editor_state(indoc! {"
25024        case \"$1\" in
25025            start)
25026                echo \"foo bar\"
25027                ;;
25028            *)ˇ
25029    "});
25030
25031    // test `fi` outdents to correct level with nested if blocks
25032    cx.set_state(indoc! {"
25033        if [ \"$1\" = \"test\" ]; then
25034            echo \"outer if\"
25035            if [ \"$2\" = \"debug\" ]; then
25036                echo \"inner if\"
25037                ˇ
25038    "});
25039    cx.update_editor(|editor, window, cx| {
25040        editor.handle_input("fi", window, cx);
25041    });
25042    cx.assert_editor_state(indoc! {"
25043        if [ \"$1\" = \"test\" ]; then
25044            echo \"outer if\"
25045            if [ \"$2\" = \"debug\" ]; then
25046                echo \"inner if\"
25047            fiˇ
25048    "});
25049}
25050
25051#[gpui::test]
25052async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25053    init_test(cx, |_| {});
25054    update_test_language_settings(cx, |settings| {
25055        settings.defaults.extend_comment_on_newline = Some(false);
25056    });
25057    let mut cx = EditorTestContext::new(cx).await;
25058    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25059    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25060
25061    // test correct indent after newline on comment
25062    cx.set_state(indoc! {"
25063        # COMMENT:ˇ
25064    "});
25065    cx.update_editor(|editor, window, cx| {
25066        editor.newline(&Newline, window, cx);
25067    });
25068    cx.assert_editor_state(indoc! {"
25069        # COMMENT:
25070        ˇ
25071    "});
25072
25073    // test correct indent after newline after `then`
25074    cx.set_state(indoc! {"
25075
25076        if [ \"$1\" = \"test\" ]; thenˇ
25077    "});
25078    cx.update_editor(|editor, window, cx| {
25079        editor.newline(&Newline, window, cx);
25080    });
25081    cx.run_until_parked();
25082    cx.assert_editor_state(indoc! {"
25083
25084        if [ \"$1\" = \"test\" ]; then
25085            ˇ
25086    "});
25087
25088    // test correct indent after newline after `else`
25089    cx.set_state(indoc! {"
25090        if [ \"$1\" = \"test\" ]; then
25091        elseˇ
25092    "});
25093    cx.update_editor(|editor, window, cx| {
25094        editor.newline(&Newline, window, cx);
25095    });
25096    cx.run_until_parked();
25097    cx.assert_editor_state(indoc! {"
25098        if [ \"$1\" = \"test\" ]; then
25099        else
25100            ˇ
25101    "});
25102
25103    // test correct indent after newline after `elif`
25104    cx.set_state(indoc! {"
25105        if [ \"$1\" = \"test\" ]; then
25106        elifˇ
25107    "});
25108    cx.update_editor(|editor, window, cx| {
25109        editor.newline(&Newline, window, cx);
25110    });
25111    cx.run_until_parked();
25112    cx.assert_editor_state(indoc! {"
25113        if [ \"$1\" = \"test\" ]; then
25114        elif
25115            ˇ
25116    "});
25117
25118    // test correct indent after newline after `do`
25119    cx.set_state(indoc! {"
25120        for file in *.txt; doˇ
25121    "});
25122    cx.update_editor(|editor, window, cx| {
25123        editor.newline(&Newline, window, cx);
25124    });
25125    cx.run_until_parked();
25126    cx.assert_editor_state(indoc! {"
25127        for file in *.txt; do
25128            ˇ
25129    "});
25130
25131    // test correct indent after newline after case pattern
25132    cx.set_state(indoc! {"
25133        case \"$1\" in
25134            start)ˇ
25135    "});
25136    cx.update_editor(|editor, window, cx| {
25137        editor.newline(&Newline, window, cx);
25138    });
25139    cx.run_until_parked();
25140    cx.assert_editor_state(indoc! {"
25141        case \"$1\" in
25142            start)
25143                ˇ
25144    "});
25145
25146    // test correct indent after newline after case pattern
25147    cx.set_state(indoc! {"
25148        case \"$1\" in
25149            start)
25150                ;;
25151            *)ˇ
25152    "});
25153    cx.update_editor(|editor, window, cx| {
25154        editor.newline(&Newline, window, cx);
25155    });
25156    cx.run_until_parked();
25157    cx.assert_editor_state(indoc! {"
25158        case \"$1\" in
25159            start)
25160                ;;
25161            *)
25162                ˇ
25163    "});
25164
25165    // test correct indent after newline after function opening brace
25166    cx.set_state(indoc! {"
25167        function test() {ˇ}
25168    "});
25169    cx.update_editor(|editor, window, cx| {
25170        editor.newline(&Newline, window, cx);
25171    });
25172    cx.run_until_parked();
25173    cx.assert_editor_state(indoc! {"
25174        function test() {
25175            ˇ
25176        }
25177    "});
25178
25179    // test no extra indent after semicolon on same line
25180    cx.set_state(indoc! {"
25181        echo \"test\"25182    "});
25183    cx.update_editor(|editor, window, cx| {
25184        editor.newline(&Newline, window, cx);
25185    });
25186    cx.run_until_parked();
25187    cx.assert_editor_state(indoc! {"
25188        echo \"test\";
25189        ˇ
25190    "});
25191}
25192
25193fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25194    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25195    point..point
25196}
25197
25198#[track_caller]
25199fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25200    let (text, ranges) = marked_text_ranges(marked_text, true);
25201    assert_eq!(editor.text(cx), text);
25202    assert_eq!(
25203        editor.selections.ranges(cx),
25204        ranges,
25205        "Assert selections are {}",
25206        marked_text
25207    );
25208}
25209
25210pub fn handle_signature_help_request(
25211    cx: &mut EditorLspTestContext,
25212    mocked_response: lsp::SignatureHelp,
25213) -> impl Future<Output = ()> + use<> {
25214    let mut request =
25215        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25216            let mocked_response = mocked_response.clone();
25217            async move { Ok(Some(mocked_response)) }
25218        });
25219
25220    async move {
25221        request.next().await;
25222    }
25223}
25224
25225#[track_caller]
25226pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25227    cx.update_editor(|editor, _, _| {
25228        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25229            let entries = menu.entries.borrow();
25230            let entries = entries
25231                .iter()
25232                .map(|entry| entry.string.as_str())
25233                .collect::<Vec<_>>();
25234            assert_eq!(entries, expected);
25235        } else {
25236            panic!("Expected completions menu");
25237        }
25238    });
25239}
25240
25241/// Handle completion request passing a marked string specifying where the completion
25242/// should be triggered from using '|' character, what range should be replaced, and what completions
25243/// should be returned using '<' and '>' to delimit the range.
25244///
25245/// Also see `handle_completion_request_with_insert_and_replace`.
25246#[track_caller]
25247pub fn handle_completion_request(
25248    marked_string: &str,
25249    completions: Vec<&'static str>,
25250    is_incomplete: bool,
25251    counter: Arc<AtomicUsize>,
25252    cx: &mut EditorLspTestContext,
25253) -> impl Future<Output = ()> {
25254    let complete_from_marker: TextRangeMarker = '|'.into();
25255    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25256    let (_, mut marked_ranges) = marked_text_ranges_by(
25257        marked_string,
25258        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25259    );
25260
25261    let complete_from_position =
25262        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25263    let replace_range =
25264        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25265
25266    let mut request =
25267        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25268            let completions = completions.clone();
25269            counter.fetch_add(1, atomic::Ordering::Release);
25270            async move {
25271                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25272                assert_eq!(
25273                    params.text_document_position.position,
25274                    complete_from_position
25275                );
25276                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25277                    is_incomplete,
25278                    item_defaults: None,
25279                    items: completions
25280                        .iter()
25281                        .map(|completion_text| lsp::CompletionItem {
25282                            label: completion_text.to_string(),
25283                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25284                                range: replace_range,
25285                                new_text: completion_text.to_string(),
25286                            })),
25287                            ..Default::default()
25288                        })
25289                        .collect(),
25290                })))
25291            }
25292        });
25293
25294    async move {
25295        request.next().await;
25296    }
25297}
25298
25299/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25300/// given instead, which also contains an `insert` range.
25301///
25302/// This function uses markers to define ranges:
25303/// - `|` marks the cursor position
25304/// - `<>` marks the replace range
25305/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25306pub fn handle_completion_request_with_insert_and_replace(
25307    cx: &mut EditorLspTestContext,
25308    marked_string: &str,
25309    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25310    counter: Arc<AtomicUsize>,
25311) -> impl Future<Output = ()> {
25312    let complete_from_marker: TextRangeMarker = '|'.into();
25313    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25314    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25315
25316    let (_, mut marked_ranges) = marked_text_ranges_by(
25317        marked_string,
25318        vec![
25319            complete_from_marker.clone(),
25320            replace_range_marker.clone(),
25321            insert_range_marker.clone(),
25322        ],
25323    );
25324
25325    let complete_from_position =
25326        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25327    let replace_range =
25328        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25329
25330    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25331        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25332        _ => lsp::Range {
25333            start: replace_range.start,
25334            end: complete_from_position,
25335        },
25336    };
25337
25338    let mut request =
25339        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25340            let completions = completions.clone();
25341            counter.fetch_add(1, atomic::Ordering::Release);
25342            async move {
25343                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25344                assert_eq!(
25345                    params.text_document_position.position, complete_from_position,
25346                    "marker `|` position doesn't match",
25347                );
25348                Ok(Some(lsp::CompletionResponse::Array(
25349                    completions
25350                        .iter()
25351                        .map(|(label, new_text)| lsp::CompletionItem {
25352                            label: label.to_string(),
25353                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25354                                lsp::InsertReplaceEdit {
25355                                    insert: insert_range,
25356                                    replace: replace_range,
25357                                    new_text: new_text.to_string(),
25358                                },
25359                            )),
25360                            ..Default::default()
25361                        })
25362                        .collect(),
25363                )))
25364            }
25365        });
25366
25367    async move {
25368        request.next().await;
25369    }
25370}
25371
25372fn handle_resolve_completion_request(
25373    cx: &mut EditorLspTestContext,
25374    edits: Option<Vec<(&'static str, &'static str)>>,
25375) -> impl Future<Output = ()> {
25376    let edits = edits.map(|edits| {
25377        edits
25378            .iter()
25379            .map(|(marked_string, new_text)| {
25380                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25381                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25382                lsp::TextEdit::new(replace_range, new_text.to_string())
25383            })
25384            .collect::<Vec<_>>()
25385    });
25386
25387    let mut request =
25388        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25389            let edits = edits.clone();
25390            async move {
25391                Ok(lsp::CompletionItem {
25392                    additional_text_edits: edits,
25393                    ..Default::default()
25394                })
25395            }
25396        });
25397
25398    async move {
25399        request.next().await;
25400    }
25401}
25402
25403pub(crate) fn update_test_language_settings(
25404    cx: &mut TestAppContext,
25405    f: impl Fn(&mut AllLanguageSettingsContent),
25406) {
25407    cx.update(|cx| {
25408        SettingsStore::update_global(cx, |store, cx| {
25409            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25410        });
25411    });
25412}
25413
25414pub(crate) fn update_test_project_settings(
25415    cx: &mut TestAppContext,
25416    f: impl Fn(&mut ProjectSettingsContent),
25417) {
25418    cx.update(|cx| {
25419        SettingsStore::update_global(cx, |store, cx| {
25420            store.update_user_settings(cx, |settings| f(&mut settings.project));
25421        });
25422    });
25423}
25424
25425pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25426    cx.update(|cx| {
25427        assets::Assets.load_test_fonts(cx);
25428        let store = SettingsStore::test(cx);
25429        cx.set_global(store);
25430        theme::init(theme::LoadThemes::JustBase, cx);
25431        release_channel::init(SemanticVersion::default(), cx);
25432        client::init_settings(cx);
25433        language::init(cx);
25434        Project::init_settings(cx);
25435        workspace::init_settings(cx);
25436        crate::init(cx);
25437    });
25438    zlog::init_test();
25439    update_test_language_settings(cx, f);
25440}
25441
25442#[track_caller]
25443fn assert_hunk_revert(
25444    not_reverted_text_with_selections: &str,
25445    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25446    expected_reverted_text_with_selections: &str,
25447    base_text: &str,
25448    cx: &mut EditorLspTestContext,
25449) {
25450    cx.set_state(not_reverted_text_with_selections);
25451    cx.set_head_text(base_text);
25452    cx.executor().run_until_parked();
25453
25454    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25455        let snapshot = editor.snapshot(window, cx);
25456        let reverted_hunk_statuses = snapshot
25457            .buffer_snapshot()
25458            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25459            .map(|hunk| hunk.status().kind)
25460            .collect::<Vec<_>>();
25461
25462        editor.git_restore(&Default::default(), window, cx);
25463        reverted_hunk_statuses
25464    });
25465    cx.executor().run_until_parked();
25466    cx.assert_editor_state(expected_reverted_text_with_selections);
25467    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25468}
25469
25470#[gpui::test(iterations = 10)]
25471async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25472    init_test(cx, |_| {});
25473
25474    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25475    let counter = diagnostic_requests.clone();
25476
25477    let fs = FakeFs::new(cx.executor());
25478    fs.insert_tree(
25479        path!("/a"),
25480        json!({
25481            "first.rs": "fn main() { let a = 5; }",
25482            "second.rs": "// Test file",
25483        }),
25484    )
25485    .await;
25486
25487    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25488    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25489    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25490
25491    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25492    language_registry.add(rust_lang());
25493    let mut fake_servers = language_registry.register_fake_lsp(
25494        "Rust",
25495        FakeLspAdapter {
25496            capabilities: lsp::ServerCapabilities {
25497                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25498                    lsp::DiagnosticOptions {
25499                        identifier: None,
25500                        inter_file_dependencies: true,
25501                        workspace_diagnostics: true,
25502                        work_done_progress_options: Default::default(),
25503                    },
25504                )),
25505                ..Default::default()
25506            },
25507            ..Default::default()
25508        },
25509    );
25510
25511    let editor = workspace
25512        .update(cx, |workspace, window, cx| {
25513            workspace.open_abs_path(
25514                PathBuf::from(path!("/a/first.rs")),
25515                OpenOptions::default(),
25516                window,
25517                cx,
25518            )
25519        })
25520        .unwrap()
25521        .await
25522        .unwrap()
25523        .downcast::<Editor>()
25524        .unwrap();
25525    let fake_server = fake_servers.next().await.unwrap();
25526    let server_id = fake_server.server.server_id();
25527    let mut first_request = fake_server
25528        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25529            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25530            let result_id = Some(new_result_id.to_string());
25531            assert_eq!(
25532                params.text_document.uri,
25533                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25534            );
25535            async move {
25536                Ok(lsp::DocumentDiagnosticReportResult::Report(
25537                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25538                        related_documents: None,
25539                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25540                            items: Vec::new(),
25541                            result_id,
25542                        },
25543                    }),
25544                ))
25545            }
25546        });
25547
25548    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25549        project.update(cx, |project, cx| {
25550            let buffer_id = editor
25551                .read(cx)
25552                .buffer()
25553                .read(cx)
25554                .as_singleton()
25555                .expect("created a singleton buffer")
25556                .read(cx)
25557                .remote_id();
25558            let buffer_result_id = project
25559                .lsp_store()
25560                .read(cx)
25561                .result_id(server_id, buffer_id, cx);
25562            assert_eq!(expected, buffer_result_id);
25563        });
25564    };
25565
25566    ensure_result_id(None, cx);
25567    cx.executor().advance_clock(Duration::from_millis(60));
25568    cx.executor().run_until_parked();
25569    assert_eq!(
25570        diagnostic_requests.load(atomic::Ordering::Acquire),
25571        1,
25572        "Opening file should trigger diagnostic request"
25573    );
25574    first_request
25575        .next()
25576        .await
25577        .expect("should have sent the first diagnostics pull request");
25578    ensure_result_id(Some("1".to_string()), cx);
25579
25580    // Editing should trigger diagnostics
25581    editor.update_in(cx, |editor, window, cx| {
25582        editor.handle_input("2", window, cx)
25583    });
25584    cx.executor().advance_clock(Duration::from_millis(60));
25585    cx.executor().run_until_parked();
25586    assert_eq!(
25587        diagnostic_requests.load(atomic::Ordering::Acquire),
25588        2,
25589        "Editing should trigger diagnostic request"
25590    );
25591    ensure_result_id(Some("2".to_string()), cx);
25592
25593    // Moving cursor should not trigger diagnostic request
25594    editor.update_in(cx, |editor, window, cx| {
25595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25596            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25597        });
25598    });
25599    cx.executor().advance_clock(Duration::from_millis(60));
25600    cx.executor().run_until_parked();
25601    assert_eq!(
25602        diagnostic_requests.load(atomic::Ordering::Acquire),
25603        2,
25604        "Cursor movement should not trigger diagnostic request"
25605    );
25606    ensure_result_id(Some("2".to_string()), cx);
25607    // Multiple rapid edits should be debounced
25608    for _ in 0..5 {
25609        editor.update_in(cx, |editor, window, cx| {
25610            editor.handle_input("x", window, cx)
25611        });
25612    }
25613    cx.executor().advance_clock(Duration::from_millis(60));
25614    cx.executor().run_until_parked();
25615
25616    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25617    assert!(
25618        final_requests <= 4,
25619        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25620    );
25621    ensure_result_id(Some(final_requests.to_string()), cx);
25622}
25623
25624#[gpui::test]
25625async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25626    // Regression test for issue #11671
25627    // Previously, adding a cursor after moving multiple cursors would reset
25628    // the cursor count instead of adding to the existing cursors.
25629    init_test(cx, |_| {});
25630    let mut cx = EditorTestContext::new(cx).await;
25631
25632    // Create a simple buffer with cursor at start
25633    cx.set_state(indoc! {"
25634        ˇaaaa
25635        bbbb
25636        cccc
25637        dddd
25638        eeee
25639        ffff
25640        gggg
25641        hhhh"});
25642
25643    // Add 2 cursors below (so we have 3 total)
25644    cx.update_editor(|editor, window, cx| {
25645        editor.add_selection_below(&Default::default(), window, cx);
25646        editor.add_selection_below(&Default::default(), window, cx);
25647    });
25648
25649    // Verify we have 3 cursors
25650    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25651    assert_eq!(
25652        initial_count, 3,
25653        "Should have 3 cursors after adding 2 below"
25654    );
25655
25656    // Move down one line
25657    cx.update_editor(|editor, window, cx| {
25658        editor.move_down(&MoveDown, window, cx);
25659    });
25660
25661    // Add another cursor below
25662    cx.update_editor(|editor, window, cx| {
25663        editor.add_selection_below(&Default::default(), window, cx);
25664    });
25665
25666    // Should now have 4 cursors (3 original + 1 new)
25667    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25668    assert_eq!(
25669        final_count, 4,
25670        "Should have 4 cursors after moving and adding another"
25671    );
25672}
25673
25674#[gpui::test(iterations = 10)]
25675async fn test_document_colors(cx: &mut TestAppContext) {
25676    let expected_color = Rgba {
25677        r: 0.33,
25678        g: 0.33,
25679        b: 0.33,
25680        a: 0.33,
25681    };
25682
25683    init_test(cx, |_| {});
25684
25685    let fs = FakeFs::new(cx.executor());
25686    fs.insert_tree(
25687        path!("/a"),
25688        json!({
25689            "first.rs": "fn main() { let a = 5; }",
25690        }),
25691    )
25692    .await;
25693
25694    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25695    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25696    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25697
25698    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25699    language_registry.add(rust_lang());
25700    let mut fake_servers = language_registry.register_fake_lsp(
25701        "Rust",
25702        FakeLspAdapter {
25703            capabilities: lsp::ServerCapabilities {
25704                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25705                ..lsp::ServerCapabilities::default()
25706            },
25707            name: "rust-analyzer",
25708            ..FakeLspAdapter::default()
25709        },
25710    );
25711    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25712        "Rust",
25713        FakeLspAdapter {
25714            capabilities: lsp::ServerCapabilities {
25715                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25716                ..lsp::ServerCapabilities::default()
25717            },
25718            name: "not-rust-analyzer",
25719            ..FakeLspAdapter::default()
25720        },
25721    );
25722
25723    let editor = workspace
25724        .update(cx, |workspace, window, cx| {
25725            workspace.open_abs_path(
25726                PathBuf::from(path!("/a/first.rs")),
25727                OpenOptions::default(),
25728                window,
25729                cx,
25730            )
25731        })
25732        .unwrap()
25733        .await
25734        .unwrap()
25735        .downcast::<Editor>()
25736        .unwrap();
25737    let fake_language_server = fake_servers.next().await.unwrap();
25738    let fake_language_server_without_capabilities =
25739        fake_servers_without_capabilities.next().await.unwrap();
25740    let requests_made = Arc::new(AtomicUsize::new(0));
25741    let closure_requests_made = Arc::clone(&requests_made);
25742    let mut color_request_handle = fake_language_server
25743        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25744            let requests_made = Arc::clone(&closure_requests_made);
25745            async move {
25746                assert_eq!(
25747                    params.text_document.uri,
25748                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25749                );
25750                requests_made.fetch_add(1, atomic::Ordering::Release);
25751                Ok(vec![
25752                    lsp::ColorInformation {
25753                        range: lsp::Range {
25754                            start: lsp::Position {
25755                                line: 0,
25756                                character: 0,
25757                            },
25758                            end: lsp::Position {
25759                                line: 0,
25760                                character: 1,
25761                            },
25762                        },
25763                        color: lsp::Color {
25764                            red: 0.33,
25765                            green: 0.33,
25766                            blue: 0.33,
25767                            alpha: 0.33,
25768                        },
25769                    },
25770                    lsp::ColorInformation {
25771                        range: lsp::Range {
25772                            start: lsp::Position {
25773                                line: 0,
25774                                character: 0,
25775                            },
25776                            end: lsp::Position {
25777                                line: 0,
25778                                character: 1,
25779                            },
25780                        },
25781                        color: lsp::Color {
25782                            red: 0.33,
25783                            green: 0.33,
25784                            blue: 0.33,
25785                            alpha: 0.33,
25786                        },
25787                    },
25788                ])
25789            }
25790        });
25791
25792    let _handle = fake_language_server_without_capabilities
25793        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25794            panic!("Should not be called");
25795        });
25796    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25797    color_request_handle.next().await.unwrap();
25798    cx.run_until_parked();
25799    assert_eq!(
25800        1,
25801        requests_made.load(atomic::Ordering::Acquire),
25802        "Should query for colors once per editor open"
25803    );
25804    editor.update_in(cx, |editor, _, cx| {
25805        assert_eq!(
25806            vec![expected_color],
25807            extract_color_inlays(editor, cx),
25808            "Should have an initial inlay"
25809        );
25810    });
25811
25812    // opening another file in a split should not influence the LSP query counter
25813    workspace
25814        .update(cx, |workspace, window, cx| {
25815            assert_eq!(
25816                workspace.panes().len(),
25817                1,
25818                "Should have one pane with one editor"
25819            );
25820            workspace.move_item_to_pane_in_direction(
25821                &MoveItemToPaneInDirection {
25822                    direction: SplitDirection::Right,
25823                    focus: false,
25824                    clone: true,
25825                },
25826                window,
25827                cx,
25828            );
25829        })
25830        .unwrap();
25831    cx.run_until_parked();
25832    workspace
25833        .update(cx, |workspace, _, cx| {
25834            let panes = workspace.panes();
25835            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25836            for pane in panes {
25837                let editor = pane
25838                    .read(cx)
25839                    .active_item()
25840                    .and_then(|item| item.downcast::<Editor>())
25841                    .expect("Should have opened an editor in each split");
25842                let editor_file = editor
25843                    .read(cx)
25844                    .buffer()
25845                    .read(cx)
25846                    .as_singleton()
25847                    .expect("test deals with singleton buffers")
25848                    .read(cx)
25849                    .file()
25850                    .expect("test buffese should have a file")
25851                    .path();
25852                assert_eq!(
25853                    editor_file.as_ref(),
25854                    rel_path("first.rs"),
25855                    "Both editors should be opened for the same file"
25856                )
25857            }
25858        })
25859        .unwrap();
25860
25861    cx.executor().advance_clock(Duration::from_millis(500));
25862    let save = editor.update_in(cx, |editor, window, cx| {
25863        editor.move_to_end(&MoveToEnd, window, cx);
25864        editor.handle_input("dirty", window, cx);
25865        editor.save(
25866            SaveOptions {
25867                format: true,
25868                autosave: true,
25869            },
25870            project.clone(),
25871            window,
25872            cx,
25873        )
25874    });
25875    save.await.unwrap();
25876
25877    color_request_handle.next().await.unwrap();
25878    cx.run_until_parked();
25879    assert_eq!(
25880        2,
25881        requests_made.load(atomic::Ordering::Acquire),
25882        "Should query for colors once per save (deduplicated) and once per formatting after save"
25883    );
25884
25885    drop(editor);
25886    let close = workspace
25887        .update(cx, |workspace, window, cx| {
25888            workspace.active_pane().update(cx, |pane, cx| {
25889                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25890            })
25891        })
25892        .unwrap();
25893    close.await.unwrap();
25894    let close = workspace
25895        .update(cx, |workspace, window, cx| {
25896            workspace.active_pane().update(cx, |pane, cx| {
25897                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25898            })
25899        })
25900        .unwrap();
25901    close.await.unwrap();
25902    assert_eq!(
25903        2,
25904        requests_made.load(atomic::Ordering::Acquire),
25905        "After saving and closing all editors, no extra requests should be made"
25906    );
25907    workspace
25908        .update(cx, |workspace, _, cx| {
25909            assert!(
25910                workspace.active_item(cx).is_none(),
25911                "Should close all editors"
25912            )
25913        })
25914        .unwrap();
25915
25916    workspace
25917        .update(cx, |workspace, window, cx| {
25918            workspace.active_pane().update(cx, |pane, cx| {
25919                pane.navigate_backward(&workspace::GoBack, window, cx);
25920            })
25921        })
25922        .unwrap();
25923    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25924    cx.run_until_parked();
25925    let editor = workspace
25926        .update(cx, |workspace, _, cx| {
25927            workspace
25928                .active_item(cx)
25929                .expect("Should have reopened the editor again after navigating back")
25930                .downcast::<Editor>()
25931                .expect("Should be an editor")
25932        })
25933        .unwrap();
25934
25935    assert_eq!(
25936        2,
25937        requests_made.load(atomic::Ordering::Acquire),
25938        "Cache should be reused on buffer close and reopen"
25939    );
25940    editor.update(cx, |editor, cx| {
25941        assert_eq!(
25942            vec![expected_color],
25943            extract_color_inlays(editor, cx),
25944            "Should have an initial inlay"
25945        );
25946    });
25947
25948    drop(color_request_handle);
25949    let closure_requests_made = Arc::clone(&requests_made);
25950    let mut empty_color_request_handle = fake_language_server
25951        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25952            let requests_made = Arc::clone(&closure_requests_made);
25953            async move {
25954                assert_eq!(
25955                    params.text_document.uri,
25956                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25957                );
25958                requests_made.fetch_add(1, atomic::Ordering::Release);
25959                Ok(Vec::new())
25960            }
25961        });
25962    let save = editor.update_in(cx, |editor, window, cx| {
25963        editor.move_to_end(&MoveToEnd, window, cx);
25964        editor.handle_input("dirty_again", window, cx);
25965        editor.save(
25966            SaveOptions {
25967                format: false,
25968                autosave: true,
25969            },
25970            project.clone(),
25971            window,
25972            cx,
25973        )
25974    });
25975    save.await.unwrap();
25976
25977    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25978    empty_color_request_handle.next().await.unwrap();
25979    cx.run_until_parked();
25980    assert_eq!(
25981        3,
25982        requests_made.load(atomic::Ordering::Acquire),
25983        "Should query for colors once per save only, as formatting was not requested"
25984    );
25985    editor.update(cx, |editor, cx| {
25986        assert_eq!(
25987            Vec::<Rgba>::new(),
25988            extract_color_inlays(editor, cx),
25989            "Should clear all colors when the server returns an empty response"
25990        );
25991    });
25992}
25993
25994#[gpui::test]
25995async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25996    init_test(cx, |_| {});
25997    let (editor, cx) = cx.add_window_view(Editor::single_line);
25998    editor.update_in(cx, |editor, window, cx| {
25999        editor.set_text("oops\n\nwow\n", window, cx)
26000    });
26001    cx.run_until_parked();
26002    editor.update(cx, |editor, cx| {
26003        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26004    });
26005    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26006    cx.run_until_parked();
26007    editor.update(cx, |editor, cx| {
26008        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26009    });
26010}
26011
26012#[gpui::test]
26013async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26014    init_test(cx, |_| {});
26015
26016    cx.update(|cx| {
26017        register_project_item::<Editor>(cx);
26018    });
26019
26020    let fs = FakeFs::new(cx.executor());
26021    fs.insert_tree("/root1", json!({})).await;
26022    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26023        .await;
26024
26025    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26026    let (workspace, cx) =
26027        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26028
26029    let worktree_id = project.update(cx, |project, cx| {
26030        project.worktrees(cx).next().unwrap().read(cx).id()
26031    });
26032
26033    let handle = workspace
26034        .update_in(cx, |workspace, window, cx| {
26035            let project_path = (worktree_id, rel_path("one.pdf"));
26036            workspace.open_path(project_path, None, true, window, cx)
26037        })
26038        .await
26039        .unwrap();
26040
26041    assert_eq!(
26042        handle.to_any().entity_type(),
26043        TypeId::of::<InvalidBufferView>()
26044    );
26045}
26046
26047#[gpui::test]
26048async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26049    init_test(cx, |_| {});
26050
26051    let language = Arc::new(Language::new(
26052        LanguageConfig::default(),
26053        Some(tree_sitter_rust::LANGUAGE.into()),
26054    ));
26055
26056    // Test hierarchical sibling navigation
26057    let text = r#"
26058        fn outer() {
26059            if condition {
26060                let a = 1;
26061            }
26062            let b = 2;
26063        }
26064
26065        fn another() {
26066            let c = 3;
26067        }
26068    "#;
26069
26070    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26071    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26072    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26073
26074    // Wait for parsing to complete
26075    editor
26076        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26077        .await;
26078
26079    editor.update_in(cx, |editor, window, cx| {
26080        // Start by selecting "let a = 1;" inside the if block
26081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26082            s.select_display_ranges([
26083                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26084            ]);
26085        });
26086
26087        let initial_selection = editor.selections.display_ranges(cx);
26088        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26089
26090        // Test select next sibling - should move up levels to find the next sibling
26091        // Since "let a = 1;" has no siblings in the if block, it should move up
26092        // to find "let b = 2;" which is a sibling of the if block
26093        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26094        let next_selection = editor.selections.display_ranges(cx);
26095
26096        // Should have a selection and it should be different from the initial
26097        assert_eq!(
26098            next_selection.len(),
26099            1,
26100            "Should have one selection after next"
26101        );
26102        assert_ne!(
26103            next_selection[0], initial_selection[0],
26104            "Next sibling selection should be different"
26105        );
26106
26107        // Test hierarchical navigation by going to the end of the current function
26108        // and trying to navigate to the next function
26109        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26110            s.select_display_ranges([
26111                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26112            ]);
26113        });
26114
26115        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26116        let function_next_selection = editor.selections.display_ranges(cx);
26117
26118        // Should move to the next function
26119        assert_eq!(
26120            function_next_selection.len(),
26121            1,
26122            "Should have one selection after function next"
26123        );
26124
26125        // Test select previous sibling navigation
26126        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26127        let prev_selection = editor.selections.display_ranges(cx);
26128
26129        // Should have a selection and it should be different
26130        assert_eq!(
26131            prev_selection.len(),
26132            1,
26133            "Should have one selection after prev"
26134        );
26135        assert_ne!(
26136            prev_selection[0], function_next_selection[0],
26137            "Previous sibling selection should be different from next"
26138        );
26139    });
26140}
26141
26142#[gpui::test]
26143async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26144    init_test(cx, |_| {});
26145
26146    let mut cx = EditorTestContext::new(cx).await;
26147    cx.set_state(
26148        "let ˇvariable = 42;
26149let another = variable + 1;
26150let result = variable * 2;",
26151    );
26152
26153    // Set up document highlights manually (simulating LSP response)
26154    cx.update_editor(|editor, _window, cx| {
26155        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26156
26157        // Create highlights for "variable" occurrences
26158        let highlight_ranges = [
26159            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26160            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26161            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26162        ];
26163
26164        let anchor_ranges: Vec<_> = highlight_ranges
26165            .iter()
26166            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26167            .collect();
26168
26169        editor.highlight_background::<DocumentHighlightRead>(
26170            &anchor_ranges,
26171            |theme| theme.colors().editor_document_highlight_read_background,
26172            cx,
26173        );
26174    });
26175
26176    // Go to next highlight - should move to second "variable"
26177    cx.update_editor(|editor, window, cx| {
26178        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26179    });
26180    cx.assert_editor_state(
26181        "let variable = 42;
26182let another = ˇvariable + 1;
26183let result = variable * 2;",
26184    );
26185
26186    // Go to next highlight - should move to third "variable"
26187    cx.update_editor(|editor, window, cx| {
26188        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26189    });
26190    cx.assert_editor_state(
26191        "let variable = 42;
26192let another = variable + 1;
26193let result = ˇvariable * 2;",
26194    );
26195
26196    // Go to next highlight - should stay at third "variable" (no wrap-around)
26197    cx.update_editor(|editor, window, cx| {
26198        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26199    });
26200    cx.assert_editor_state(
26201        "let variable = 42;
26202let another = variable + 1;
26203let result = ˇvariable * 2;",
26204    );
26205
26206    // Now test going backwards from third position
26207    cx.update_editor(|editor, window, cx| {
26208        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26209    });
26210    cx.assert_editor_state(
26211        "let variable = 42;
26212let another = ˇvariable + 1;
26213let result = variable * 2;",
26214    );
26215
26216    // Go to previous highlight - should move to first "variable"
26217    cx.update_editor(|editor, window, cx| {
26218        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26219    });
26220    cx.assert_editor_state(
26221        "let ˇvariable = 42;
26222let another = variable + 1;
26223let result = variable * 2;",
26224    );
26225
26226    // Go to previous highlight - should stay on first "variable"
26227    cx.update_editor(|editor, window, cx| {
26228        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26229    });
26230    cx.assert_editor_state(
26231        "let ˇvariable = 42;
26232let another = variable + 1;
26233let result = variable * 2;",
26234    );
26235}
26236
26237#[gpui::test]
26238async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26239    cx: &mut gpui::TestAppContext,
26240) {
26241    init_test(cx, |_| {});
26242
26243    let url = "https://zed.dev";
26244
26245    let markdown_language = Arc::new(Language::new(
26246        LanguageConfig {
26247            name: "Markdown".into(),
26248            ..LanguageConfig::default()
26249        },
26250        None,
26251    ));
26252
26253    let mut cx = EditorTestContext::new(cx).await;
26254    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26255    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26256
26257    cx.update_editor(|editor, window, cx| {
26258        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26259        editor.paste(&Paste, window, cx);
26260    });
26261
26262    cx.assert_editor_state(&format!(
26263        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26264    ));
26265}
26266
26267#[gpui::test]
26268async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26269    cx: &mut gpui::TestAppContext,
26270) {
26271    init_test(cx, |_| {});
26272
26273    let url = "https://zed.dev";
26274
26275    let markdown_language = Arc::new(Language::new(
26276        LanguageConfig {
26277            name: "Markdown".into(),
26278            ..LanguageConfig::default()
26279        },
26280        None,
26281    ));
26282
26283    let mut cx = EditorTestContext::new(cx).await;
26284    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26285    cx.set_state(&format!(
26286        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26287    ));
26288
26289    cx.update_editor(|editor, window, cx| {
26290        editor.copy(&Copy, window, cx);
26291    });
26292
26293    cx.set_state(&format!(
26294        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26295    ));
26296
26297    cx.update_editor(|editor, window, cx| {
26298        editor.paste(&Paste, window, cx);
26299    });
26300
26301    cx.assert_editor_state(&format!(
26302        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26303    ));
26304}
26305
26306#[gpui::test]
26307async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26308    cx: &mut gpui::TestAppContext,
26309) {
26310    init_test(cx, |_| {});
26311
26312    let url = "https://zed.dev";
26313
26314    let markdown_language = Arc::new(Language::new(
26315        LanguageConfig {
26316            name: "Markdown".into(),
26317            ..LanguageConfig::default()
26318        },
26319        None,
26320    ));
26321
26322    let mut cx = EditorTestContext::new(cx).await;
26323    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26324    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26325
26326    cx.update_editor(|editor, window, cx| {
26327        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26328        editor.paste(&Paste, window, cx);
26329    });
26330
26331    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26332}
26333
26334#[gpui::test]
26335async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26336    cx: &mut gpui::TestAppContext,
26337) {
26338    init_test(cx, |_| {});
26339
26340    let text = "Awesome";
26341
26342    let markdown_language = Arc::new(Language::new(
26343        LanguageConfig {
26344            name: "Markdown".into(),
26345            ..LanguageConfig::default()
26346        },
26347        None,
26348    ));
26349
26350    let mut cx = EditorTestContext::new(cx).await;
26351    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26352    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26353
26354    cx.update_editor(|editor, window, cx| {
26355        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26356        editor.paste(&Paste, window, cx);
26357    });
26358
26359    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26360}
26361
26362#[gpui::test]
26363async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26364    cx: &mut gpui::TestAppContext,
26365) {
26366    init_test(cx, |_| {});
26367
26368    let url = "https://zed.dev";
26369
26370    let markdown_language = Arc::new(Language::new(
26371        LanguageConfig {
26372            name: "Rust".into(),
26373            ..LanguageConfig::default()
26374        },
26375        None,
26376    ));
26377
26378    let mut cx = EditorTestContext::new(cx).await;
26379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26380    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26381
26382    cx.update_editor(|editor, window, cx| {
26383        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26384        editor.paste(&Paste, window, cx);
26385    });
26386
26387    cx.assert_editor_state(&format!(
26388        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26389    ));
26390}
26391
26392#[gpui::test]
26393async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26394    cx: &mut TestAppContext,
26395) {
26396    init_test(cx, |_| {});
26397
26398    let url = "https://zed.dev";
26399
26400    let markdown_language = Arc::new(Language::new(
26401        LanguageConfig {
26402            name: "Markdown".into(),
26403            ..LanguageConfig::default()
26404        },
26405        None,
26406    ));
26407
26408    let (editor, cx) = cx.add_window_view(|window, cx| {
26409        let multi_buffer = MultiBuffer::build_multi(
26410            [
26411                ("this will embed -> link", vec![Point::row_range(0..1)]),
26412                ("this will replace -> link", vec![Point::row_range(0..1)]),
26413            ],
26414            cx,
26415        );
26416        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26417        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26418            s.select_ranges(vec![
26419                Point::new(0, 19)..Point::new(0, 23),
26420                Point::new(1, 21)..Point::new(1, 25),
26421            ])
26422        });
26423        let first_buffer_id = multi_buffer
26424            .read(cx)
26425            .excerpt_buffer_ids()
26426            .into_iter()
26427            .next()
26428            .unwrap();
26429        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26430        first_buffer.update(cx, |buffer, cx| {
26431            buffer.set_language(Some(markdown_language.clone()), cx);
26432        });
26433
26434        editor
26435    });
26436    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26437
26438    cx.update_editor(|editor, window, cx| {
26439        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26440        editor.paste(&Paste, window, cx);
26441    });
26442
26443    cx.assert_editor_state(&format!(
26444        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26445    ));
26446}
26447
26448#[gpui::test]
26449async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26450    init_test(cx, |_| {});
26451
26452    let fs = FakeFs::new(cx.executor());
26453    fs.insert_tree(
26454        path!("/project"),
26455        json!({
26456            "first.rs": "# First Document\nSome content here.",
26457            "second.rs": "Plain text content for second file.",
26458        }),
26459    )
26460    .await;
26461
26462    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26463    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26464    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26465
26466    let language = rust_lang();
26467    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26468    language_registry.add(language.clone());
26469    let mut fake_servers = language_registry.register_fake_lsp(
26470        "Rust",
26471        FakeLspAdapter {
26472            ..FakeLspAdapter::default()
26473        },
26474    );
26475
26476    let buffer1 = project
26477        .update(cx, |project, cx| {
26478            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26479        })
26480        .await
26481        .unwrap();
26482    let buffer2 = project
26483        .update(cx, |project, cx| {
26484            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26485        })
26486        .await
26487        .unwrap();
26488
26489    let multi_buffer = cx.new(|cx| {
26490        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26491        multi_buffer.set_excerpts_for_path(
26492            PathKey::for_buffer(&buffer1, cx),
26493            buffer1.clone(),
26494            [Point::zero()..buffer1.read(cx).max_point()],
26495            3,
26496            cx,
26497        );
26498        multi_buffer.set_excerpts_for_path(
26499            PathKey::for_buffer(&buffer2, cx),
26500            buffer2.clone(),
26501            [Point::zero()..buffer1.read(cx).max_point()],
26502            3,
26503            cx,
26504        );
26505        multi_buffer
26506    });
26507
26508    let (editor, cx) = cx.add_window_view(|window, cx| {
26509        Editor::new(
26510            EditorMode::full(),
26511            multi_buffer,
26512            Some(project.clone()),
26513            window,
26514            cx,
26515        )
26516    });
26517
26518    let fake_language_server = fake_servers.next().await.unwrap();
26519
26520    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26521
26522    let save = editor.update_in(cx, |editor, window, cx| {
26523        assert!(editor.is_dirty(cx));
26524
26525        editor.save(
26526            SaveOptions {
26527                format: true,
26528                autosave: true,
26529            },
26530            project,
26531            window,
26532            cx,
26533        )
26534    });
26535    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26536    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26537    let mut done_edit_rx = Some(done_edit_rx);
26538    let mut start_edit_tx = Some(start_edit_tx);
26539
26540    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26541        start_edit_tx.take().unwrap().send(()).unwrap();
26542        let done_edit_rx = done_edit_rx.take().unwrap();
26543        async move {
26544            done_edit_rx.await.unwrap();
26545            Ok(None)
26546        }
26547    });
26548
26549    start_edit_rx.await.unwrap();
26550    buffer2
26551        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26552        .unwrap();
26553
26554    done_edit_tx.send(()).unwrap();
26555
26556    save.await.unwrap();
26557    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26558}
26559
26560#[track_caller]
26561fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26562    editor
26563        .all_inlays(cx)
26564        .into_iter()
26565        .filter_map(|inlay| inlay.get_color())
26566        .map(Rgba::from)
26567        .collect()
26568}
26569
26570#[gpui::test]
26571fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26572    init_test(cx, |_| {});
26573
26574    let editor = cx.add_window(|window, cx| {
26575        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26576        build_editor(buffer, window, cx)
26577    });
26578
26579    editor
26580        .update(cx, |editor, window, cx| {
26581            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26582                s.select_display_ranges([
26583                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26584                ])
26585            });
26586
26587            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26588
26589            assert_eq!(
26590                editor.display_text(cx),
26591                "line1\nline2\nline2",
26592                "Duplicating last line upward should create duplicate above, not on same line"
26593            );
26594
26595            assert_eq!(
26596                editor.selections.display_ranges(cx),
26597                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26598                "Selection should remain on the original line"
26599            );
26600        })
26601        .unwrap();
26602}
26603
26604#[gpui::test]
26605async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26606    init_test(cx, |_| {});
26607
26608    let mut cx = EditorTestContext::new(cx).await;
26609
26610    cx.set_state("line1\nline2ˇ");
26611
26612    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26613
26614    let clipboard_text = cx
26615        .read_from_clipboard()
26616        .and_then(|item| item.text().as_deref().map(str::to_string));
26617
26618    assert_eq!(
26619        clipboard_text,
26620        Some("line2\n".to_string()),
26621        "Copying a line without trailing newline should include a newline"
26622    );
26623
26624    cx.set_state("line1\nˇ");
26625
26626    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26627
26628    cx.assert_editor_state("line1\nline2\nˇ");
26629}