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), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4391                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 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    let editor = cx.add_window(|window, cx| {
 4415        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4416        build_editor(buffer, window, cx)
 4417    });
 4418    _ = editor.update(cx, |editor, window, cx| {
 4419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4420            s.select_display_ranges([
 4421                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4422            ])
 4423        });
 4424        editor.delete_line(&DeleteLine, window, cx);
 4425        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4426        assert_eq!(
 4427            editor.selections.display_ranges(cx),
 4428            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4429        );
 4430    });
 4431}
 4432
 4433#[gpui::test]
 4434fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4435    init_test(cx, |_| {});
 4436
 4437    cx.add_window(|window, cx| {
 4438        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4439        let mut editor = build_editor(buffer.clone(), window, cx);
 4440        let buffer = buffer.read(cx).as_singleton().unwrap();
 4441
 4442        assert_eq!(
 4443            editor.selections.ranges::<Point>(cx),
 4444            &[Point::new(0, 0)..Point::new(0, 0)]
 4445        );
 4446
 4447        // When on single line, replace newline at end by space
 4448        editor.join_lines(&JoinLines, window, cx);
 4449        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4450        assert_eq!(
 4451            editor.selections.ranges::<Point>(cx),
 4452            &[Point::new(0, 3)..Point::new(0, 3)]
 4453        );
 4454
 4455        // When multiple lines are selected, remove newlines that are spanned by the selection
 4456        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4457            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4458        });
 4459        editor.join_lines(&JoinLines, window, cx);
 4460        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4461        assert_eq!(
 4462            editor.selections.ranges::<Point>(cx),
 4463            &[Point::new(0, 11)..Point::new(0, 11)]
 4464        );
 4465
 4466        // Undo should be transactional
 4467        editor.undo(&Undo, window, cx);
 4468        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4469        assert_eq!(
 4470            editor.selections.ranges::<Point>(cx),
 4471            &[Point::new(0, 5)..Point::new(2, 2)]
 4472        );
 4473
 4474        // When joining an empty line don't insert a space
 4475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4476            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4477        });
 4478        editor.join_lines(&JoinLines, window, cx);
 4479        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4480        assert_eq!(
 4481            editor.selections.ranges::<Point>(cx),
 4482            [Point::new(2, 3)..Point::new(2, 3)]
 4483        );
 4484
 4485        // We can remove trailing newlines
 4486        editor.join_lines(&JoinLines, window, cx);
 4487        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4488        assert_eq!(
 4489            editor.selections.ranges::<Point>(cx),
 4490            [Point::new(2, 3)..Point::new(2, 3)]
 4491        );
 4492
 4493        // We don't blow up on the last line
 4494        editor.join_lines(&JoinLines, window, cx);
 4495        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4496        assert_eq!(
 4497            editor.selections.ranges::<Point>(cx),
 4498            [Point::new(2, 3)..Point::new(2, 3)]
 4499        );
 4500
 4501        // reset to test indentation
 4502        editor.buffer.update(cx, |buffer, cx| {
 4503            buffer.edit(
 4504                [
 4505                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4506                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4507                ],
 4508                None,
 4509                cx,
 4510            )
 4511        });
 4512
 4513        // We remove any leading spaces
 4514        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4516            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4517        });
 4518        editor.join_lines(&JoinLines, window, cx);
 4519        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4520
 4521        // We don't insert a space for a line containing only spaces
 4522        editor.join_lines(&JoinLines, window, cx);
 4523        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4524
 4525        // We ignore any leading tabs
 4526        editor.join_lines(&JoinLines, window, cx);
 4527        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4528
 4529        editor
 4530    });
 4531}
 4532
 4533#[gpui::test]
 4534fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4535    init_test(cx, |_| {});
 4536
 4537    cx.add_window(|window, cx| {
 4538        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4539        let mut editor = build_editor(buffer.clone(), window, cx);
 4540        let buffer = buffer.read(cx).as_singleton().unwrap();
 4541
 4542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4543            s.select_ranges([
 4544                Point::new(0, 2)..Point::new(1, 1),
 4545                Point::new(1, 2)..Point::new(1, 2),
 4546                Point::new(3, 1)..Point::new(3, 2),
 4547            ])
 4548        });
 4549
 4550        editor.join_lines(&JoinLines, window, cx);
 4551        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4552
 4553        assert_eq!(
 4554            editor.selections.ranges::<Point>(cx),
 4555            [
 4556                Point::new(0, 7)..Point::new(0, 7),
 4557                Point::new(1, 3)..Point::new(1, 3)
 4558            ]
 4559        );
 4560        editor
 4561    });
 4562}
 4563
 4564#[gpui::test]
 4565async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4566    init_test(cx, |_| {});
 4567
 4568    let mut cx = EditorTestContext::new(cx).await;
 4569
 4570    let diff_base = r#"
 4571        Line 0
 4572        Line 1
 4573        Line 2
 4574        Line 3
 4575        "#
 4576    .unindent();
 4577
 4578    cx.set_state(
 4579        &r#"
 4580        ˇLine 0
 4581        Line 1
 4582        Line 2
 4583        Line 3
 4584        "#
 4585        .unindent(),
 4586    );
 4587
 4588    cx.set_head_text(&diff_base);
 4589    executor.run_until_parked();
 4590
 4591    // Join lines
 4592    cx.update_editor(|editor, window, cx| {
 4593        editor.join_lines(&JoinLines, window, cx);
 4594    });
 4595    executor.run_until_parked();
 4596
 4597    cx.assert_editor_state(
 4598        &r#"
 4599        Line 0ˇ Line 1
 4600        Line 2
 4601        Line 3
 4602        "#
 4603        .unindent(),
 4604    );
 4605    // Join again
 4606    cx.update_editor(|editor, window, cx| {
 4607        editor.join_lines(&JoinLines, window, cx);
 4608    });
 4609    executor.run_until_parked();
 4610
 4611    cx.assert_editor_state(
 4612        &r#"
 4613        Line 0 Line 1ˇ Line 2
 4614        Line 3
 4615        "#
 4616        .unindent(),
 4617    );
 4618}
 4619
 4620#[gpui::test]
 4621async fn test_custom_newlines_cause_no_false_positive_diffs(
 4622    executor: BackgroundExecutor,
 4623    cx: &mut TestAppContext,
 4624) {
 4625    init_test(cx, |_| {});
 4626    let mut cx = EditorTestContext::new(cx).await;
 4627    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4628    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4629    executor.run_until_parked();
 4630
 4631    cx.update_editor(|editor, window, cx| {
 4632        let snapshot = editor.snapshot(window, cx);
 4633        assert_eq!(
 4634            snapshot
 4635                .buffer_snapshot()
 4636                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4637                .collect::<Vec<_>>(),
 4638            Vec::new(),
 4639            "Should not have any diffs for files with custom newlines"
 4640        );
 4641    });
 4642}
 4643
 4644#[gpui::test]
 4645async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4646    init_test(cx, |_| {});
 4647
 4648    let mut cx = EditorTestContext::new(cx).await;
 4649
 4650    // Test sort_lines_case_insensitive()
 4651    cx.set_state(indoc! {"
 4652        «z
 4653        y
 4654        x
 4655        Z
 4656        Y
 4657        Xˇ»
 4658    "});
 4659    cx.update_editor(|e, window, cx| {
 4660        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4661    });
 4662    cx.assert_editor_state(indoc! {"
 4663        «x
 4664        X
 4665        y
 4666        Y
 4667        z
 4668        Zˇ»
 4669    "});
 4670
 4671    // Test sort_lines_by_length()
 4672    //
 4673    // Demonstrates:
 4674    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4675    // - sort is stable
 4676    cx.set_state(indoc! {"
 4677        «123
 4678        æ
 4679        12
 4680 4681        1
 4682        æˇ»
 4683    "});
 4684    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4685    cx.assert_editor_state(indoc! {"
 4686        «æ
 4687 4688        1
 4689        æ
 4690        12
 4691        123ˇ»
 4692    "});
 4693
 4694    // Test reverse_lines()
 4695    cx.set_state(indoc! {"
 4696        «5
 4697        4
 4698        3
 4699        2
 4700        1ˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «1
 4705        2
 4706        3
 4707        4
 4708        5ˇ»
 4709    "});
 4710
 4711    // Skip testing shuffle_line()
 4712
 4713    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4714    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4715
 4716    // Don't manipulate when cursor is on single line, but expand the selection
 4717    cx.set_state(indoc! {"
 4718        ddˇdd
 4719        ccc
 4720        bb
 4721        a
 4722    "});
 4723    cx.update_editor(|e, window, cx| {
 4724        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4725    });
 4726    cx.assert_editor_state(indoc! {"
 4727        «ddddˇ»
 4728        ccc
 4729        bb
 4730        a
 4731    "});
 4732
 4733    // Basic manipulate case
 4734    // Start selection moves to column 0
 4735    // End of selection shrinks to fit shorter line
 4736    cx.set_state(indoc! {"
 4737        dd«d
 4738        ccc
 4739        bb
 4740        aaaaaˇ»
 4741    "});
 4742    cx.update_editor(|e, window, cx| {
 4743        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4744    });
 4745    cx.assert_editor_state(indoc! {"
 4746        «aaaaa
 4747        bb
 4748        ccc
 4749        dddˇ»
 4750    "});
 4751
 4752    // Manipulate case with newlines
 4753    cx.set_state(indoc! {"
 4754        dd«d
 4755        ccc
 4756
 4757        bb
 4758        aaaaa
 4759
 4760        ˇ»
 4761    "});
 4762    cx.update_editor(|e, window, cx| {
 4763        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4764    });
 4765    cx.assert_editor_state(indoc! {"
 4766        «
 4767
 4768        aaaaa
 4769        bb
 4770        ccc
 4771        dddˇ»
 4772
 4773    "});
 4774
 4775    // Adding new line
 4776    cx.set_state(indoc! {"
 4777        aa«a
 4778        bbˇ»b
 4779    "});
 4780    cx.update_editor(|e, window, cx| {
 4781        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4782    });
 4783    cx.assert_editor_state(indoc! {"
 4784        «aaa
 4785        bbb
 4786        added_lineˇ»
 4787    "});
 4788
 4789    // Removing line
 4790    cx.set_state(indoc! {"
 4791        aa«a
 4792        bbbˇ»
 4793    "});
 4794    cx.update_editor(|e, window, cx| {
 4795        e.manipulate_immutable_lines(window, cx, |lines| {
 4796            lines.pop();
 4797        })
 4798    });
 4799    cx.assert_editor_state(indoc! {"
 4800        «aaaˇ»
 4801    "});
 4802
 4803    // Removing all lines
 4804    cx.set_state(indoc! {"
 4805        aa«a
 4806        bbbˇ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| {
 4809        e.manipulate_immutable_lines(window, cx, |lines| {
 4810            lines.drain(..);
 4811        })
 4812    });
 4813    cx.assert_editor_state(indoc! {"
 4814        ˇ
 4815    "});
 4816}
 4817
 4818#[gpui::test]
 4819async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4820    init_test(cx, |_| {});
 4821
 4822    let mut cx = EditorTestContext::new(cx).await;
 4823
 4824    // Consider continuous selection as single selection
 4825    cx.set_state(indoc! {"
 4826        Aaa«aa
 4827        cˇ»c«c
 4828        bb
 4829        aaaˇ»aa
 4830    "});
 4831    cx.update_editor(|e, window, cx| {
 4832        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4833    });
 4834    cx.assert_editor_state(indoc! {"
 4835        «Aaaaa
 4836        ccc
 4837        bb
 4838        aaaaaˇ»
 4839    "});
 4840
 4841    cx.set_state(indoc! {"
 4842        Aaa«aa
 4843        cˇ»c«c
 4844        bb
 4845        aaaˇ»aa
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «Aaaaa
 4852        ccc
 4853        bbˇ»
 4854    "});
 4855
 4856    // Consider non continuous selection as distinct dedup operations
 4857    cx.set_state(indoc! {"
 4858        «aaaaa
 4859        bb
 4860        aaaaa
 4861        aaaaaˇ»
 4862
 4863        aaa«aaˇ»
 4864    "});
 4865    cx.update_editor(|e, window, cx| {
 4866        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4867    });
 4868    cx.assert_editor_state(indoc! {"
 4869        «aaaaa
 4870        bbˇ»
 4871
 4872        «aaaaaˇ»
 4873    "});
 4874}
 4875
 4876#[gpui::test]
 4877async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4878    init_test(cx, |_| {});
 4879
 4880    let mut cx = EditorTestContext::new(cx).await;
 4881
 4882    cx.set_state(indoc! {"
 4883        «Aaa
 4884        aAa
 4885        Aaaˇ»
 4886    "});
 4887    cx.update_editor(|e, window, cx| {
 4888        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4889    });
 4890    cx.assert_editor_state(indoc! {"
 4891        «Aaa
 4892        aAaˇ»
 4893    "});
 4894
 4895    cx.set_state(indoc! {"
 4896        «Aaa
 4897        aAa
 4898        aaAˇ»
 4899    "});
 4900    cx.update_editor(|e, window, cx| {
 4901        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4902    });
 4903    cx.assert_editor_state(indoc! {"
 4904        «Aaaˇ»
 4905    "});
 4906}
 4907
 4908#[gpui::test]
 4909async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4910    init_test(cx, |_| {});
 4911
 4912    let mut cx = EditorTestContext::new(cx).await;
 4913
 4914    let js_language = Arc::new(Language::new(
 4915        LanguageConfig {
 4916            name: "JavaScript".into(),
 4917            wrap_characters: Some(language::WrapCharactersConfig {
 4918                start_prefix: "<".into(),
 4919                start_suffix: ">".into(),
 4920                end_prefix: "</".into(),
 4921                end_suffix: ">".into(),
 4922            }),
 4923            ..LanguageConfig::default()
 4924        },
 4925        None,
 4926    ));
 4927
 4928    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4929
 4930    cx.set_state(indoc! {"
 4931        «testˇ»
 4932    "});
 4933    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4934    cx.assert_editor_state(indoc! {"
 4935        <«ˇ»>test</«ˇ»>
 4936    "});
 4937
 4938    cx.set_state(indoc! {"
 4939        «test
 4940         testˇ»
 4941    "});
 4942    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4943    cx.assert_editor_state(indoc! {"
 4944        <«ˇ»>test
 4945         test</«ˇ»>
 4946    "});
 4947
 4948    cx.set_state(indoc! {"
 4949        teˇst
 4950    "});
 4951    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4952    cx.assert_editor_state(indoc! {"
 4953        te<«ˇ»></«ˇ»>st
 4954    "});
 4955}
 4956
 4957#[gpui::test]
 4958async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4959    init_test(cx, |_| {});
 4960
 4961    let mut cx = EditorTestContext::new(cx).await;
 4962
 4963    let js_language = Arc::new(Language::new(
 4964        LanguageConfig {
 4965            name: "JavaScript".into(),
 4966            wrap_characters: Some(language::WrapCharactersConfig {
 4967                start_prefix: "<".into(),
 4968                start_suffix: ">".into(),
 4969                end_prefix: "</".into(),
 4970                end_suffix: ">".into(),
 4971            }),
 4972            ..LanguageConfig::default()
 4973        },
 4974        None,
 4975    ));
 4976
 4977    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4978
 4979    cx.set_state(indoc! {"
 4980        «testˇ»
 4981        «testˇ» «testˇ»
 4982        «testˇ»
 4983    "});
 4984    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4985    cx.assert_editor_state(indoc! {"
 4986        <«ˇ»>test</«ˇ»>
 4987        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4988        <«ˇ»>test</«ˇ»>
 4989    "});
 4990
 4991    cx.set_state(indoc! {"
 4992        «test
 4993         testˇ»
 4994        «test
 4995         testˇ»
 4996    "});
 4997    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4998    cx.assert_editor_state(indoc! {"
 4999        <«ˇ»>test
 5000         test</«ˇ»>
 5001        <«ˇ»>test
 5002         test</«ˇ»>
 5003    "});
 5004}
 5005
 5006#[gpui::test]
 5007async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5008    init_test(cx, |_| {});
 5009
 5010    let mut cx = EditorTestContext::new(cx).await;
 5011
 5012    let plaintext_language = Arc::new(Language::new(
 5013        LanguageConfig {
 5014            name: "Plain Text".into(),
 5015            ..LanguageConfig::default()
 5016        },
 5017        None,
 5018    ));
 5019
 5020    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5021
 5022    cx.set_state(indoc! {"
 5023        «testˇ»
 5024    "});
 5025    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5026    cx.assert_editor_state(indoc! {"
 5027      «testˇ»
 5028    "});
 5029}
 5030
 5031#[gpui::test]
 5032async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5033    init_test(cx, |_| {});
 5034
 5035    let mut cx = EditorTestContext::new(cx).await;
 5036
 5037    // Manipulate with multiple selections on a single line
 5038    cx.set_state(indoc! {"
 5039        dd«dd
 5040        cˇ»c«c
 5041        bb
 5042        aaaˇ»aa
 5043    "});
 5044    cx.update_editor(|e, window, cx| {
 5045        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5046    });
 5047    cx.assert_editor_state(indoc! {"
 5048        «aaaaa
 5049        bb
 5050        ccc
 5051        ddddˇ»
 5052    "});
 5053
 5054    // Manipulate with multiple disjoin selections
 5055    cx.set_state(indoc! {"
 5056 5057        4
 5058        3
 5059        2
 5060        1ˇ»
 5061
 5062        dd«dd
 5063        ccc
 5064        bb
 5065        aaaˇ»aa
 5066    "});
 5067    cx.update_editor(|e, window, cx| {
 5068        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5069    });
 5070    cx.assert_editor_state(indoc! {"
 5071        «1
 5072        2
 5073        3
 5074        4
 5075        5ˇ»
 5076
 5077        «aaaaa
 5078        bb
 5079        ccc
 5080        ddddˇ»
 5081    "});
 5082
 5083    // Adding lines on each selection
 5084    cx.set_state(indoc! {"
 5085 5086        1ˇ»
 5087
 5088        bb«bb
 5089        aaaˇ»aa
 5090    "});
 5091    cx.update_editor(|e, window, cx| {
 5092        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5093    });
 5094    cx.assert_editor_state(indoc! {"
 5095        «2
 5096        1
 5097        added lineˇ»
 5098
 5099        «bbbb
 5100        aaaaa
 5101        added lineˇ»
 5102    "});
 5103
 5104    // Removing lines on each selection
 5105    cx.set_state(indoc! {"
 5106 5107        1ˇ»
 5108
 5109        bb«bb
 5110        aaaˇ»aa
 5111    "});
 5112    cx.update_editor(|e, window, cx| {
 5113        e.manipulate_immutable_lines(window, cx, |lines| {
 5114            lines.pop();
 5115        })
 5116    });
 5117    cx.assert_editor_state(indoc! {"
 5118        «2ˇ»
 5119
 5120        «bbbbˇ»
 5121    "});
 5122}
 5123
 5124#[gpui::test]
 5125async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5126    init_test(cx, |settings| {
 5127        settings.defaults.tab_size = NonZeroU32::new(3)
 5128    });
 5129
 5130    let mut cx = EditorTestContext::new(cx).await;
 5131
 5132    // MULTI SELECTION
 5133    // Ln.1 "«" tests empty lines
 5134    // Ln.9 tests just leading whitespace
 5135    cx.set_state(indoc! {"
 5136        «
 5137        abc                 // No indentationˇ»
 5138        «\tabc              // 1 tabˇ»
 5139        \t\tabc «      ˇ»   // 2 tabs
 5140        \t ab«c             // Tab followed by space
 5141         \tabc              // Space followed by tab (3 spaces should be the result)
 5142        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5143           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5144        \t
 5145        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5146    "});
 5147    cx.update_editor(|e, window, cx| {
 5148        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5149    });
 5150    cx.assert_editor_state(
 5151        indoc! {"
 5152            «
 5153            abc                 // No indentation
 5154               abc              // 1 tab
 5155                  abc          // 2 tabs
 5156                abc             // Tab followed by space
 5157               abc              // Space followed by tab (3 spaces should be the result)
 5158                           abc   // Mixed indentation (tab conversion depends on the column)
 5159               abc         // Already space indented
 5160               ·
 5161               abc\tdef          // Only the leading tab is manipulatedˇ»
 5162        "}
 5163        .replace("·", "")
 5164        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5165    );
 5166
 5167    // Test on just a few lines, the others should remain unchanged
 5168    // Only lines (3, 5, 10, 11) should change
 5169    cx.set_state(
 5170        indoc! {"
 5171            ·
 5172            abc                 // No indentation
 5173            \tabcˇ               // 1 tab
 5174            \t\tabc             // 2 tabs
 5175            \t abcˇ              // Tab followed by space
 5176             \tabc              // Space followed by tab (3 spaces should be the result)
 5177            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5178               abc              // Already space indented
 5179            «\t
 5180            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5181        "}
 5182        .replace("·", "")
 5183        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5184    );
 5185    cx.update_editor(|e, window, cx| {
 5186        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5187    });
 5188    cx.assert_editor_state(
 5189        indoc! {"
 5190            ·
 5191            abc                 // No indentation
 5192            «   abc               // 1 tabˇ»
 5193            \t\tabc             // 2 tabs
 5194            «    abc              // Tab followed by spaceˇ»
 5195             \tabc              // Space followed by tab (3 spaces should be the result)
 5196            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5197               abc              // Already space indented
 5198            «   ·
 5199               abc\tdef          // Only the leading tab is manipulatedˇ»
 5200        "}
 5201        .replace("·", "")
 5202        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5203    );
 5204
 5205    // SINGLE SELECTION
 5206    // Ln.1 "«" tests empty lines
 5207    // Ln.9 tests just leading whitespace
 5208    cx.set_state(indoc! {"
 5209        «
 5210        abc                 // No indentation
 5211        \tabc               // 1 tab
 5212        \t\tabc             // 2 tabs
 5213        \t abc              // Tab followed by space
 5214         \tabc              // Space followed by tab (3 spaces should be the result)
 5215        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5216           abc              // Already space indented
 5217        \t
 5218        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5219    "});
 5220    cx.update_editor(|e, window, cx| {
 5221        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5222    });
 5223    cx.assert_editor_state(
 5224        indoc! {"
 5225            «
 5226            abc                 // No indentation
 5227               abc               // 1 tab
 5228                  abc             // 2 tabs
 5229                abc              // Tab followed by space
 5230               abc              // Space followed by tab (3 spaces should be the result)
 5231                           abc   // Mixed indentation (tab conversion depends on the column)
 5232               abc              // Already space indented
 5233               ·
 5234               abc\tdef          // Only the leading tab is manipulatedˇ»
 5235        "}
 5236        .replace("·", "")
 5237        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5238    );
 5239}
 5240
 5241#[gpui::test]
 5242async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5243    init_test(cx, |settings| {
 5244        settings.defaults.tab_size = NonZeroU32::new(3)
 5245    });
 5246
 5247    let mut cx = EditorTestContext::new(cx).await;
 5248
 5249    // MULTI SELECTION
 5250    // Ln.1 "«" tests empty lines
 5251    // Ln.11 tests just leading whitespace
 5252    cx.set_state(indoc! {"
 5253        «
 5254        abˇ»ˇc                 // No indentation
 5255         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5256          abc  «             // 2 spaces (< 3 so dont convert)
 5257           abc              // 3 spaces (convert)
 5258             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5259        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5260        «\t abc              // Tab followed by space
 5261         \tabc              // Space followed by tab (should be consumed due to tab)
 5262        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5263           \tˇ»  «\t
 5264           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5265    "});
 5266    cx.update_editor(|e, window, cx| {
 5267        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5268    });
 5269    cx.assert_editor_state(indoc! {"
 5270        «
 5271        abc                 // No indentation
 5272         abc                // 1 space (< 3 so dont convert)
 5273          abc               // 2 spaces (< 3 so dont convert)
 5274        \tabc              // 3 spaces (convert)
 5275        \t  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\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5280        \t\t\t
 5281        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5282    "});
 5283
 5284    // Test on just a few lines, the other should remain unchanged
 5285    // Only lines (4, 8, 11, 12) should change
 5286    cx.set_state(
 5287        indoc! {"
 5288            ·
 5289            abc                 // No indentation
 5290             abc                // 1 space (< 3 so dont convert)
 5291              abc               // 2 spaces (< 3 so dont convert)
 5292            «   abc              // 3 spaces (convert)ˇ»
 5293                 abc            // 5 spaces (1 tab + 2 spaces)
 5294            \t\t\tabc           // Already tab indented
 5295            \t abc              // Tab followed by space
 5296             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5297               \t\t  \tabc      // Mixed indentation
 5298            \t \t  \t   \tabc   // Mixed indentation
 5299               \t  \tˇ
 5300            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5301        "}
 5302        .replace("·", "")
 5303        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5304    );
 5305    cx.update_editor(|e, window, cx| {
 5306        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5307    });
 5308    cx.assert_editor_state(
 5309        indoc! {"
 5310            ·
 5311            abc                 // No indentation
 5312             abc                // 1 space (< 3 so dont convert)
 5313              abc               // 2 spaces (< 3 so dont convert)
 5314            «\tabc              // 3 spaces (convert)ˇ»
 5315                 abc            // 5 spaces (1 tab + 2 spaces)
 5316            \t\t\tabc           // Already tab indented
 5317            \t abc              // Tab followed by space
 5318            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5319               \t\t  \tabc      // Mixed indentation
 5320            \t \t  \t   \tabc   // Mixed indentation
 5321            «\t\t\t
 5322            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5323        "}
 5324        .replace("·", "")
 5325        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5326    );
 5327
 5328    // SINGLE SELECTION
 5329    // Ln.1 "«" tests empty lines
 5330    // Ln.11 tests just leading whitespace
 5331    cx.set_state(indoc! {"
 5332        «
 5333        abc                 // No indentation
 5334         abc                // 1 space (< 3 so dont convert)
 5335          abc               // 2 spaces (< 3 so dont convert)
 5336           abc              // 3 spaces (convert)
 5337             abc            // 5 spaces (1 tab + 2 spaces)
 5338        \t\t\tabc           // Already tab indented
 5339        \t abc              // Tab followed by space
 5340         \tabc              // Space followed by tab (should be consumed due to tab)
 5341        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5342           \t  \t
 5343           abc   \t         // Only the leading spaces should be convertedˇ»
 5344    "});
 5345    cx.update_editor(|e, window, cx| {
 5346        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5347    });
 5348    cx.assert_editor_state(indoc! {"
 5349        «
 5350        abc                 // No indentation
 5351         abc                // 1 space (< 3 so dont convert)
 5352          abc               // 2 spaces (< 3 so dont convert)
 5353        \tabc              // 3 spaces (convert)
 5354        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5355        \t\t\tabc           // Already tab indented
 5356        \t abc              // Tab followed by space
 5357        \tabc              // Space followed by tab (should be consumed due to tab)
 5358        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5359        \t\t\t
 5360        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5361    "});
 5362}
 5363
 5364#[gpui::test]
 5365async fn test_toggle_case(cx: &mut TestAppContext) {
 5366    init_test(cx, |_| {});
 5367
 5368    let mut cx = EditorTestContext::new(cx).await;
 5369
 5370    // If all lower case -> upper case
 5371    cx.set_state(indoc! {"
 5372        «hello worldˇ»
 5373    "});
 5374    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5375    cx.assert_editor_state(indoc! {"
 5376        «HELLO WORLDˇ»
 5377    "});
 5378
 5379    // If all upper case -> lower case
 5380    cx.set_state(indoc! {"
 5381        «HELLO WORLDˇ»
 5382    "});
 5383    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5384    cx.assert_editor_state(indoc! {"
 5385        «hello worldˇ»
 5386    "});
 5387
 5388    // If any upper case characters are identified -> lower case
 5389    // This matches JetBrains IDEs
 5390    cx.set_state(indoc! {"
 5391        «hEllo worldˇ»
 5392    "});
 5393    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5394    cx.assert_editor_state(indoc! {"
 5395        «hello worldˇ»
 5396    "});
 5397}
 5398
 5399#[gpui::test]
 5400async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5401    init_test(cx, |_| {});
 5402
 5403    let mut cx = EditorTestContext::new(cx).await;
 5404
 5405    cx.set_state(indoc! {"
 5406        «implement-windows-supportˇ»
 5407    "});
 5408    cx.update_editor(|e, window, cx| {
 5409        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5410    });
 5411    cx.assert_editor_state(indoc! {"
 5412        «Implement windows supportˇ»
 5413    "});
 5414}
 5415
 5416#[gpui::test]
 5417async fn test_manipulate_text(cx: &mut TestAppContext) {
 5418    init_test(cx, |_| {});
 5419
 5420    let mut cx = EditorTestContext::new(cx).await;
 5421
 5422    // Test convert_to_upper_case()
 5423    cx.set_state(indoc! {"
 5424        «hello worldˇ»
 5425    "});
 5426    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5427    cx.assert_editor_state(indoc! {"
 5428        «HELLO WORLDˇ»
 5429    "});
 5430
 5431    // Test convert_to_lower_case()
 5432    cx.set_state(indoc! {"
 5433        «HELLO WORLDˇ»
 5434    "});
 5435    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5436    cx.assert_editor_state(indoc! {"
 5437        «hello worldˇ»
 5438    "});
 5439
 5440    // Test multiple line, single selection case
 5441    cx.set_state(indoc! {"
 5442        «The quick brown
 5443        fox jumps over
 5444        the lazy dogˇ»
 5445    "});
 5446    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5447    cx.assert_editor_state(indoc! {"
 5448        «The Quick Brown
 5449        Fox Jumps Over
 5450        The Lazy Dogˇ»
 5451    "});
 5452
 5453    // Test multiple line, single selection case
 5454    cx.set_state(indoc! {"
 5455        «The quick brown
 5456        fox jumps over
 5457        the lazy dogˇ»
 5458    "});
 5459    cx.update_editor(|e, window, cx| {
 5460        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5461    });
 5462    cx.assert_editor_state(indoc! {"
 5463        «TheQuickBrown
 5464        FoxJumpsOver
 5465        TheLazyDogˇ»
 5466    "});
 5467
 5468    // From here on out, test more complex cases of manipulate_text()
 5469
 5470    // Test no selection case - should affect words cursors are in
 5471    // Cursor at beginning, middle, and end of word
 5472    cx.set_state(indoc! {"
 5473        ˇhello big beauˇtiful worldˇ
 5474    "});
 5475    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5476    cx.assert_editor_state(indoc! {"
 5477        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5478    "});
 5479
 5480    // Test multiple selections on a single line and across multiple lines
 5481    cx.set_state(indoc! {"
 5482        «Theˇ» quick «brown
 5483        foxˇ» jumps «overˇ»
 5484        the «lazyˇ» dog
 5485    "});
 5486    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5487    cx.assert_editor_state(indoc! {"
 5488        «THEˇ» quick «BROWN
 5489        FOXˇ» jumps «OVERˇ»
 5490        the «LAZYˇ» dog
 5491    "});
 5492
 5493    // Test case where text length grows
 5494    cx.set_state(indoc! {"
 5495        «tschüߡ»
 5496    "});
 5497    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5498    cx.assert_editor_state(indoc! {"
 5499        «TSCHÜSSˇ»
 5500    "});
 5501
 5502    // Test to make sure we don't crash when text shrinks
 5503    cx.set_state(indoc! {"
 5504        aaa_bbbˇ
 5505    "});
 5506    cx.update_editor(|e, window, cx| {
 5507        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5508    });
 5509    cx.assert_editor_state(indoc! {"
 5510        «aaaBbbˇ»
 5511    "});
 5512
 5513    // Test to make sure we all aware of the fact that each word can grow and shrink
 5514    // Final selections should be aware of this fact
 5515    cx.set_state(indoc! {"
 5516        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5517    "});
 5518    cx.update_editor(|e, window, cx| {
 5519        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5520    });
 5521    cx.assert_editor_state(indoc! {"
 5522        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5523    "});
 5524
 5525    cx.set_state(indoc! {"
 5526        «hElLo, WoRld!ˇ»
 5527    "});
 5528    cx.update_editor(|e, window, cx| {
 5529        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5530    });
 5531    cx.assert_editor_state(indoc! {"
 5532        «HeLlO, wOrLD!ˇ»
 5533    "});
 5534
 5535    // Test selections with `line_mode() = true`.
 5536    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5537    cx.set_state(indoc! {"
 5538        «The quick brown
 5539        fox jumps over
 5540        tˇ»he lazy dog
 5541    "});
 5542    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5543    cx.assert_editor_state(indoc! {"
 5544        «THE QUICK BROWN
 5545        FOX JUMPS OVER
 5546        THE LAZY DOGˇ»
 5547    "});
 5548}
 5549
 5550#[gpui::test]
 5551fn test_duplicate_line(cx: &mut TestAppContext) {
 5552    init_test(cx, |_| {});
 5553
 5554    let editor = cx.add_window(|window, cx| {
 5555        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5556        build_editor(buffer, window, cx)
 5557    });
 5558    _ = editor.update(cx, |editor, window, cx| {
 5559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5560            s.select_display_ranges([
 5561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5562                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5563                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5564                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5565            ])
 5566        });
 5567        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5568        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5569        assert_eq!(
 5570            editor.selections.display_ranges(cx),
 5571            vec![
 5572                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5574                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5575                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5576            ]
 5577        );
 5578    });
 5579
 5580    let editor = cx.add_window(|window, cx| {
 5581        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5582        build_editor(buffer, window, cx)
 5583    });
 5584    _ = editor.update(cx, |editor, window, cx| {
 5585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5586            s.select_display_ranges([
 5587                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5588                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5589            ])
 5590        });
 5591        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5592        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5593        assert_eq!(
 5594            editor.selections.display_ranges(cx),
 5595            vec![
 5596                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5597                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5598            ]
 5599        );
 5600    });
 5601
 5602    // With `move_upwards` the selections stay in place, except for
 5603    // the lines inserted above them
 5604    let editor = cx.add_window(|window, cx| {
 5605        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5606        build_editor(buffer, window, cx)
 5607    });
 5608    _ = editor.update(cx, |editor, window, cx| {
 5609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5610            s.select_display_ranges([
 5611                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5612                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5613                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5614                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5615            ])
 5616        });
 5617        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5618        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5619        assert_eq!(
 5620            editor.selections.display_ranges(cx),
 5621            vec![
 5622                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5623                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5624                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5625                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5626            ]
 5627        );
 5628    });
 5629
 5630    let editor = cx.add_window(|window, cx| {
 5631        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5632        build_editor(buffer, window, cx)
 5633    });
 5634    _ = editor.update(cx, |editor, window, cx| {
 5635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5636            s.select_display_ranges([
 5637                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5638                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5639            ])
 5640        });
 5641        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5642        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5643        assert_eq!(
 5644            editor.selections.display_ranges(cx),
 5645            vec![
 5646                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5647                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5648            ]
 5649        );
 5650    });
 5651
 5652    let editor = cx.add_window(|window, cx| {
 5653        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5654        build_editor(buffer, window, cx)
 5655    });
 5656    _ = editor.update(cx, |editor, window, cx| {
 5657        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5658            s.select_display_ranges([
 5659                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5660                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5661            ])
 5662        });
 5663        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5664        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5665        assert_eq!(
 5666            editor.selections.display_ranges(cx),
 5667            vec![
 5668                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5669                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5670            ]
 5671        );
 5672    });
 5673}
 5674
 5675#[gpui::test]
 5676fn test_move_line_up_down(cx: &mut TestAppContext) {
 5677    init_test(cx, |_| {});
 5678
 5679    let editor = cx.add_window(|window, cx| {
 5680        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5681        build_editor(buffer, window, cx)
 5682    });
 5683    _ = editor.update(cx, |editor, window, cx| {
 5684        editor.fold_creases(
 5685            vec![
 5686                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5687                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5688                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5689            ],
 5690            true,
 5691            window,
 5692            cx,
 5693        );
 5694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5695            s.select_display_ranges([
 5696                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5697                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5698                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5699                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5700            ])
 5701        });
 5702        assert_eq!(
 5703            editor.display_text(cx),
 5704            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5705        );
 5706
 5707        editor.move_line_up(&MoveLineUp, window, cx);
 5708        assert_eq!(
 5709            editor.display_text(cx),
 5710            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5711        );
 5712        assert_eq!(
 5713            editor.selections.display_ranges(cx),
 5714            vec![
 5715                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5716                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5717                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5718                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5719            ]
 5720        );
 5721    });
 5722
 5723    _ = editor.update(cx, |editor, window, cx| {
 5724        editor.move_line_down(&MoveLineDown, window, cx);
 5725        assert_eq!(
 5726            editor.display_text(cx),
 5727            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5728        );
 5729        assert_eq!(
 5730            editor.selections.display_ranges(cx),
 5731            vec![
 5732                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5733                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5734                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5735                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5736            ]
 5737        );
 5738    });
 5739
 5740    _ = editor.update(cx, |editor, window, cx| {
 5741        editor.move_line_down(&MoveLineDown, window, cx);
 5742        assert_eq!(
 5743            editor.display_text(cx),
 5744            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5745        );
 5746        assert_eq!(
 5747            editor.selections.display_ranges(cx),
 5748            vec![
 5749                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5750                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5751                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5752                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5753            ]
 5754        );
 5755    });
 5756
 5757    _ = editor.update(cx, |editor, window, cx| {
 5758        editor.move_line_up(&MoveLineUp, window, cx);
 5759        assert_eq!(
 5760            editor.display_text(cx),
 5761            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5762        );
 5763        assert_eq!(
 5764            editor.selections.display_ranges(cx),
 5765            vec![
 5766                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5767                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5768                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5769                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5770            ]
 5771        );
 5772    });
 5773}
 5774
 5775#[gpui::test]
 5776fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5777    init_test(cx, |_| {});
 5778    let editor = cx.add_window(|window, cx| {
 5779        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5780        build_editor(buffer, window, cx)
 5781    });
 5782    _ = editor.update(cx, |editor, window, cx| {
 5783        editor.fold_creases(
 5784            vec![Crease::simple(
 5785                Point::new(6, 4)..Point::new(7, 4),
 5786                FoldPlaceholder::test(),
 5787            )],
 5788            true,
 5789            window,
 5790            cx,
 5791        );
 5792        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5793            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5794        });
 5795        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5796        editor.move_line_up(&MoveLineUp, window, cx);
 5797        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5798        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5799    });
 5800}
 5801
 5802#[gpui::test]
 5803fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5804    init_test(cx, |_| {});
 5805
 5806    let editor = cx.add_window(|window, cx| {
 5807        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5808        build_editor(buffer, window, cx)
 5809    });
 5810    _ = editor.update(cx, |editor, window, cx| {
 5811        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5812        editor.insert_blocks(
 5813            [BlockProperties {
 5814                style: BlockStyle::Fixed,
 5815                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5816                height: Some(1),
 5817                render: Arc::new(|_| div().into_any()),
 5818                priority: 0,
 5819            }],
 5820            Some(Autoscroll::fit()),
 5821            cx,
 5822        );
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5825        });
 5826        editor.move_line_down(&MoveLineDown, window, cx);
 5827    });
 5828}
 5829
 5830#[gpui::test]
 5831async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5832    init_test(cx, |_| {});
 5833
 5834    let mut cx = EditorTestContext::new(cx).await;
 5835    cx.set_state(
 5836        &"
 5837            ˇzero
 5838            one
 5839            two
 5840            three
 5841            four
 5842            five
 5843        "
 5844        .unindent(),
 5845    );
 5846
 5847    // Create a four-line block that replaces three lines of text.
 5848    cx.update_editor(|editor, window, cx| {
 5849        let snapshot = editor.snapshot(window, cx);
 5850        let snapshot = &snapshot.buffer_snapshot();
 5851        let placement = BlockPlacement::Replace(
 5852            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5853        );
 5854        editor.insert_blocks(
 5855            [BlockProperties {
 5856                placement,
 5857                height: Some(4),
 5858                style: BlockStyle::Sticky,
 5859                render: Arc::new(|_| gpui::div().into_any_element()),
 5860                priority: 0,
 5861            }],
 5862            None,
 5863            cx,
 5864        );
 5865    });
 5866
 5867    // Move down so that the cursor touches the block.
 5868    cx.update_editor(|editor, window, cx| {
 5869        editor.move_down(&Default::default(), window, cx);
 5870    });
 5871    cx.assert_editor_state(
 5872        &"
 5873            zero
 5874            «one
 5875            two
 5876            threeˇ»
 5877            four
 5878            five
 5879        "
 5880        .unindent(),
 5881    );
 5882
 5883    // Move down past the block.
 5884    cx.update_editor(|editor, window, cx| {
 5885        editor.move_down(&Default::default(), window, cx);
 5886    });
 5887    cx.assert_editor_state(
 5888        &"
 5889            zero
 5890            one
 5891            two
 5892            three
 5893            ˇfour
 5894            five
 5895        "
 5896        .unindent(),
 5897    );
 5898}
 5899
 5900#[gpui::test]
 5901fn test_transpose(cx: &mut TestAppContext) {
 5902    init_test(cx, |_| {});
 5903
 5904    _ = cx.add_window(|window, cx| {
 5905        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5906        editor.set_style(EditorStyle::default(), window, cx);
 5907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5908            s.select_ranges([1..1])
 5909        });
 5910        editor.transpose(&Default::default(), window, cx);
 5911        assert_eq!(editor.text(cx), "bac");
 5912        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5913
 5914        editor.transpose(&Default::default(), window, cx);
 5915        assert_eq!(editor.text(cx), "bca");
 5916        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5917
 5918        editor.transpose(&Default::default(), window, cx);
 5919        assert_eq!(editor.text(cx), "bac");
 5920        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5921
 5922        editor
 5923    });
 5924
 5925    _ = cx.add_window(|window, cx| {
 5926        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5927        editor.set_style(EditorStyle::default(), window, cx);
 5928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5929            s.select_ranges([3..3])
 5930        });
 5931        editor.transpose(&Default::default(), window, cx);
 5932        assert_eq!(editor.text(cx), "acb\nde");
 5933        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5934
 5935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5936            s.select_ranges([4..4])
 5937        });
 5938        editor.transpose(&Default::default(), window, cx);
 5939        assert_eq!(editor.text(cx), "acbd\ne");
 5940        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5941
 5942        editor.transpose(&Default::default(), window, cx);
 5943        assert_eq!(editor.text(cx), "acbde\n");
 5944        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5945
 5946        editor.transpose(&Default::default(), window, cx);
 5947        assert_eq!(editor.text(cx), "acbd\ne");
 5948        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5949
 5950        editor
 5951    });
 5952
 5953    _ = cx.add_window(|window, cx| {
 5954        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5955        editor.set_style(EditorStyle::default(), window, cx);
 5956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5957            s.select_ranges([1..1, 2..2, 4..4])
 5958        });
 5959        editor.transpose(&Default::default(), window, cx);
 5960        assert_eq!(editor.text(cx), "bacd\ne");
 5961        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5962
 5963        editor.transpose(&Default::default(), window, cx);
 5964        assert_eq!(editor.text(cx), "bcade\n");
 5965        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5966
 5967        editor.transpose(&Default::default(), window, cx);
 5968        assert_eq!(editor.text(cx), "bcda\ne");
 5969        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5970
 5971        editor.transpose(&Default::default(), window, cx);
 5972        assert_eq!(editor.text(cx), "bcade\n");
 5973        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5974
 5975        editor.transpose(&Default::default(), window, cx);
 5976        assert_eq!(editor.text(cx), "bcaed\n");
 5977        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5978
 5979        editor
 5980    });
 5981
 5982    _ = cx.add_window(|window, cx| {
 5983        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5984        editor.set_style(EditorStyle::default(), window, cx);
 5985        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5986            s.select_ranges([4..4])
 5987        });
 5988        editor.transpose(&Default::default(), window, cx);
 5989        assert_eq!(editor.text(cx), "🏀🍐✋");
 5990        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5991
 5992        editor.transpose(&Default::default(), window, cx);
 5993        assert_eq!(editor.text(cx), "🏀✋🍐");
 5994        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5995
 5996        editor.transpose(&Default::default(), window, cx);
 5997        assert_eq!(editor.text(cx), "🏀🍐✋");
 5998        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5999
 6000        editor
 6001    });
 6002}
 6003
 6004#[gpui::test]
 6005async fn test_rewrap(cx: &mut TestAppContext) {
 6006    init_test(cx, |settings| {
 6007        settings.languages.0.extend([
 6008            (
 6009                "Markdown".into(),
 6010                LanguageSettingsContent {
 6011                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6012                    preferred_line_length: Some(40),
 6013                    ..Default::default()
 6014                },
 6015            ),
 6016            (
 6017                "Plain Text".into(),
 6018                LanguageSettingsContent {
 6019                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6020                    preferred_line_length: Some(40),
 6021                    ..Default::default()
 6022                },
 6023            ),
 6024            (
 6025                "C++".into(),
 6026                LanguageSettingsContent {
 6027                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6028                    preferred_line_length: Some(40),
 6029                    ..Default::default()
 6030                },
 6031            ),
 6032            (
 6033                "Python".into(),
 6034                LanguageSettingsContent {
 6035                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6036                    preferred_line_length: Some(40),
 6037                    ..Default::default()
 6038                },
 6039            ),
 6040            (
 6041                "Rust".into(),
 6042                LanguageSettingsContent {
 6043                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6044                    preferred_line_length: Some(40),
 6045                    ..Default::default()
 6046                },
 6047            ),
 6048        ])
 6049    });
 6050
 6051    let mut cx = EditorTestContext::new(cx).await;
 6052
 6053    let cpp_language = Arc::new(Language::new(
 6054        LanguageConfig {
 6055            name: "C++".into(),
 6056            line_comments: vec!["// ".into()],
 6057            ..LanguageConfig::default()
 6058        },
 6059        None,
 6060    ));
 6061    let python_language = Arc::new(Language::new(
 6062        LanguageConfig {
 6063            name: "Python".into(),
 6064            line_comments: vec!["# ".into()],
 6065            ..LanguageConfig::default()
 6066        },
 6067        None,
 6068    ));
 6069    let markdown_language = Arc::new(Language::new(
 6070        LanguageConfig {
 6071            name: "Markdown".into(),
 6072            rewrap_prefixes: vec![
 6073                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6074                regex::Regex::new("[-*+]\\s+").unwrap(),
 6075            ],
 6076            ..LanguageConfig::default()
 6077        },
 6078        None,
 6079    ));
 6080    let rust_language = Arc::new(
 6081        Language::new(
 6082            LanguageConfig {
 6083                name: "Rust".into(),
 6084                line_comments: vec!["// ".into(), "/// ".into()],
 6085                ..LanguageConfig::default()
 6086            },
 6087            Some(tree_sitter_rust::LANGUAGE.into()),
 6088        )
 6089        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6090        .unwrap(),
 6091    );
 6092
 6093    let plaintext_language = Arc::new(Language::new(
 6094        LanguageConfig {
 6095            name: "Plain Text".into(),
 6096            ..LanguageConfig::default()
 6097        },
 6098        None,
 6099    ));
 6100
 6101    // Test basic rewrapping of a long line with a cursor
 6102    assert_rewrap(
 6103        indoc! {"
 6104            // ˇThis is a long comment that needs to be wrapped.
 6105        "},
 6106        indoc! {"
 6107            // ˇThis is a long comment that needs to
 6108            // be wrapped.
 6109        "},
 6110        cpp_language.clone(),
 6111        &mut cx,
 6112    );
 6113
 6114    // Test rewrapping a full selection
 6115    assert_rewrap(
 6116        indoc! {"
 6117            «// This selected long comment needs to be wrapped.ˇ»"
 6118        },
 6119        indoc! {"
 6120            «// This selected long comment needs to
 6121            // be wrapped.ˇ»"
 6122        },
 6123        cpp_language.clone(),
 6124        &mut cx,
 6125    );
 6126
 6127    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6128    assert_rewrap(
 6129        indoc! {"
 6130            // ˇThis is the first line.
 6131            // Thisˇ is the second line.
 6132            // This is the thirdˇ line, all part of one paragraph.
 6133         "},
 6134        indoc! {"
 6135            // ˇThis is the first line. Thisˇ is the
 6136            // second line. This is the thirdˇ line,
 6137            // all part of one paragraph.
 6138         "},
 6139        cpp_language.clone(),
 6140        &mut cx,
 6141    );
 6142
 6143    // Test multiple cursors in different paragraphs trigger separate rewraps
 6144    assert_rewrap(
 6145        indoc! {"
 6146            // ˇThis is the first paragraph, first line.
 6147            // ˇThis is the first paragraph, second line.
 6148
 6149            // ˇThis is the second paragraph, first line.
 6150            // ˇThis is the second paragraph, second line.
 6151        "},
 6152        indoc! {"
 6153            // ˇThis is the first paragraph, first
 6154            // line. ˇThis is the first paragraph,
 6155            // second line.
 6156
 6157            // ˇThis is the second paragraph, first
 6158            // line. ˇThis is the second paragraph,
 6159            // second line.
 6160        "},
 6161        cpp_language.clone(),
 6162        &mut cx,
 6163    );
 6164
 6165    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6166    assert_rewrap(
 6167        indoc! {"
 6168            «// A regular long long comment to be wrapped.
 6169            /// A documentation long comment to be wrapped.ˇ»
 6170          "},
 6171        indoc! {"
 6172            «// A regular long long comment to be
 6173            // wrapped.
 6174            /// A documentation long comment to be
 6175            /// wrapped.ˇ»
 6176          "},
 6177        rust_language.clone(),
 6178        &mut cx,
 6179    );
 6180
 6181    // Test that change in indentation level trigger seperate rewraps
 6182    assert_rewrap(
 6183        indoc! {"
 6184            fn foo() {
 6185                «// This is a long comment at the base indent.
 6186                    // This is a long comment at the next indent.ˇ»
 6187            }
 6188        "},
 6189        indoc! {"
 6190            fn foo() {
 6191                «// This is a long comment at the
 6192                // base indent.
 6193                    // This is a long comment at the
 6194                    // next indent.ˇ»
 6195            }
 6196        "},
 6197        rust_language.clone(),
 6198        &mut cx,
 6199    );
 6200
 6201    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6202    assert_rewrap(
 6203        indoc! {"
 6204            # ˇThis is a long comment using a pound sign.
 6205        "},
 6206        indoc! {"
 6207            # ˇThis is a long comment using a pound
 6208            # sign.
 6209        "},
 6210        python_language,
 6211        &mut cx,
 6212    );
 6213
 6214    // Test rewrapping only affects comments, not code even when selected
 6215    assert_rewrap(
 6216        indoc! {"
 6217            «/// This doc comment is long and should be wrapped.
 6218            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6219        "},
 6220        indoc! {"
 6221            «/// This doc comment is long and should
 6222            /// be wrapped.
 6223            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6224        "},
 6225        rust_language.clone(),
 6226        &mut cx,
 6227    );
 6228
 6229    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6230    assert_rewrap(
 6231        indoc! {"
 6232            # Header
 6233
 6234            A long long long line of markdown text to wrap.ˇ
 6235         "},
 6236        indoc! {"
 6237            # Header
 6238
 6239            A long long long line of markdown text
 6240            to wrap.ˇ
 6241         "},
 6242        markdown_language.clone(),
 6243        &mut cx,
 6244    );
 6245
 6246    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6247    assert_rewrap(
 6248        indoc! {"
 6249            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6250            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6251            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6252        "},
 6253        indoc! {"
 6254            «1. This is a numbered list item that is
 6255               very long and needs to be wrapped
 6256               properly.
 6257            2. This is a numbered list item that is
 6258               very long and needs to be wrapped
 6259               properly.
 6260            - This is an unordered list item that is
 6261              also very long and should not merge
 6262              with the numbered item.ˇ»
 6263        "},
 6264        markdown_language.clone(),
 6265        &mut cx,
 6266    );
 6267
 6268    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6269    assert_rewrap(
 6270        indoc! {"
 6271            «1. This is a numbered list item that is
 6272            very long and needs to be wrapped
 6273            properly.
 6274            2. This is a numbered list item that is
 6275            very long and needs to be wrapped
 6276            properly.
 6277            - This is an unordered list item that is
 6278            also very long and should not merge with
 6279            the numbered item.ˇ»
 6280        "},
 6281        indoc! {"
 6282            «1. This is a numbered list item that is
 6283               very long and needs to be wrapped
 6284               properly.
 6285            2. This is a numbered list item that is
 6286               very long and needs to be wrapped
 6287               properly.
 6288            - This is an unordered list item that is
 6289              also very long and should not merge
 6290              with the numbered item.ˇ»
 6291        "},
 6292        markdown_language.clone(),
 6293        &mut cx,
 6294    );
 6295
 6296    // Test that rewrapping maintain indents even when they already exists.
 6297    assert_rewrap(
 6298        indoc! {"
 6299            «1. This is a numbered list
 6300               item that is very long and needs to be wrapped properly.
 6301            2. This is a numbered list
 6302               item that is very long and needs to be wrapped properly.
 6303            - This is an unordered list item that is also very long and
 6304              should not merge with the numbered item.ˇ»
 6305        "},
 6306        indoc! {"
 6307            «1. This is a numbered list item that is
 6308               very long and needs to be wrapped
 6309               properly.
 6310            2. This is a numbered list item that is
 6311               very long and needs to be wrapped
 6312               properly.
 6313            - This is an unordered list item that is
 6314              also very long and should not merge
 6315              with the numbered item.ˇ»
 6316        "},
 6317        markdown_language,
 6318        &mut cx,
 6319    );
 6320
 6321    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6322    assert_rewrap(
 6323        indoc! {"
 6324            ˇThis is a very long line of plain text that will be wrapped.
 6325        "},
 6326        indoc! {"
 6327            ˇThis is a very long line of plain text
 6328            that will be wrapped.
 6329        "},
 6330        plaintext_language.clone(),
 6331        &mut cx,
 6332    );
 6333
 6334    // Test that non-commented code acts as a paragraph boundary within a selection
 6335    assert_rewrap(
 6336        indoc! {"
 6337               «// This is the first long comment block to be wrapped.
 6338               fn my_func(a: u32);
 6339               // This is the second long comment block to be wrapped.ˇ»
 6340           "},
 6341        indoc! {"
 6342               «// This is the first long comment block
 6343               // to be wrapped.
 6344               fn my_func(a: u32);
 6345               // This is the second long comment block
 6346               // to be wrapped.ˇ»
 6347           "},
 6348        rust_language,
 6349        &mut cx,
 6350    );
 6351
 6352    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6353    assert_rewrap(
 6354        indoc! {"
 6355            «ˇThis is a very long line that will be wrapped.
 6356
 6357            This is another paragraph in the same selection.»
 6358
 6359            «\tThis is a very long indented line that will be wrapped.ˇ»
 6360         "},
 6361        indoc! {"
 6362            «ˇThis is a very long line that will be
 6363            wrapped.
 6364
 6365            This is another paragraph in the same
 6366            selection.»
 6367
 6368            «\tThis is a very long indented line
 6369            \tthat will be wrapped.ˇ»
 6370         "},
 6371        plaintext_language,
 6372        &mut cx,
 6373    );
 6374
 6375    // Test that an empty comment line acts as a paragraph boundary
 6376    assert_rewrap(
 6377        indoc! {"
 6378            // ˇThis is a long comment that will be wrapped.
 6379            //
 6380            // And this is another long comment that will also be wrapped.ˇ
 6381         "},
 6382        indoc! {"
 6383            // ˇThis is a long comment that will be
 6384            // wrapped.
 6385            //
 6386            // And this is another long comment that
 6387            // will also be wrapped.ˇ
 6388         "},
 6389        cpp_language,
 6390        &mut cx,
 6391    );
 6392
 6393    #[track_caller]
 6394    fn assert_rewrap(
 6395        unwrapped_text: &str,
 6396        wrapped_text: &str,
 6397        language: Arc<Language>,
 6398        cx: &mut EditorTestContext,
 6399    ) {
 6400        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6401        cx.set_state(unwrapped_text);
 6402        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6403        cx.assert_editor_state(wrapped_text);
 6404    }
 6405}
 6406
 6407#[gpui::test]
 6408async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6409    init_test(cx, |settings| {
 6410        settings.languages.0.extend([(
 6411            "Rust".into(),
 6412            LanguageSettingsContent {
 6413                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6414                preferred_line_length: Some(40),
 6415                ..Default::default()
 6416            },
 6417        )])
 6418    });
 6419
 6420    let mut cx = EditorTestContext::new(cx).await;
 6421
 6422    let rust_lang = Arc::new(
 6423        Language::new(
 6424            LanguageConfig {
 6425                name: "Rust".into(),
 6426                line_comments: vec!["// ".into()],
 6427                block_comment: Some(BlockCommentConfig {
 6428                    start: "/*".into(),
 6429                    end: "*/".into(),
 6430                    prefix: "* ".into(),
 6431                    tab_size: 1,
 6432                }),
 6433                documentation_comment: Some(BlockCommentConfig {
 6434                    start: "/**".into(),
 6435                    end: "*/".into(),
 6436                    prefix: "* ".into(),
 6437                    tab_size: 1,
 6438                }),
 6439
 6440                ..LanguageConfig::default()
 6441            },
 6442            Some(tree_sitter_rust::LANGUAGE.into()),
 6443        )
 6444        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6445        .unwrap(),
 6446    );
 6447
 6448    // regular block comment
 6449    assert_rewrap(
 6450        indoc! {"
 6451            /*
 6452             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6453             */
 6454            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6455        "},
 6456        indoc! {"
 6457            /*
 6458             *ˇ Lorem ipsum dolor sit amet,
 6459             * consectetur adipiscing elit.
 6460             */
 6461            /*
 6462             *ˇ Lorem ipsum dolor sit amet,
 6463             * consectetur adipiscing elit.
 6464             */
 6465        "},
 6466        rust_lang.clone(),
 6467        &mut cx,
 6468    );
 6469
 6470    // indent is respected
 6471    assert_rewrap(
 6472        indoc! {"
 6473            {}
 6474                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6475        "},
 6476        indoc! {"
 6477            {}
 6478                /*
 6479                 *ˇ Lorem ipsum dolor sit amet,
 6480                 * consectetur adipiscing elit.
 6481                 */
 6482        "},
 6483        rust_lang.clone(),
 6484        &mut cx,
 6485    );
 6486
 6487    // short block comments with inline delimiters
 6488    assert_rewrap(
 6489        indoc! {"
 6490            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6491            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6492             */
 6493            /*
 6494             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6495        "},
 6496        indoc! {"
 6497            /*
 6498             *ˇ Lorem ipsum dolor sit amet,
 6499             * consectetur adipiscing elit.
 6500             */
 6501            /*
 6502             *ˇ Lorem ipsum dolor sit amet,
 6503             * consectetur adipiscing elit.
 6504             */
 6505            /*
 6506             *ˇ Lorem ipsum dolor sit amet,
 6507             * consectetur adipiscing elit.
 6508             */
 6509        "},
 6510        rust_lang.clone(),
 6511        &mut cx,
 6512    );
 6513
 6514    // multiline block comment with inline start/end delimiters
 6515    assert_rewrap(
 6516        indoc! {"
 6517            /*ˇ Lorem ipsum dolor sit amet,
 6518             * consectetur adipiscing elit. */
 6519        "},
 6520        indoc! {"
 6521            /*
 6522             *ˇ Lorem ipsum dolor sit amet,
 6523             * consectetur adipiscing elit.
 6524             */
 6525        "},
 6526        rust_lang.clone(),
 6527        &mut cx,
 6528    );
 6529
 6530    // block comment rewrap still respects paragraph bounds
 6531    assert_rewrap(
 6532        indoc! {"
 6533            /*
 6534             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6535             *
 6536             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6537             */
 6538        "},
 6539        indoc! {"
 6540            /*
 6541             *ˇ Lorem ipsum dolor sit amet,
 6542             * consectetur adipiscing elit.
 6543             *
 6544             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6545             */
 6546        "},
 6547        rust_lang.clone(),
 6548        &mut cx,
 6549    );
 6550
 6551    // documentation comments
 6552    assert_rewrap(
 6553        indoc! {"
 6554            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6555            /**
 6556             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6557             */
 6558        "},
 6559        indoc! {"
 6560            /**
 6561             *ˇ Lorem ipsum dolor sit amet,
 6562             * consectetur adipiscing elit.
 6563             */
 6564            /**
 6565             *ˇ Lorem ipsum dolor sit amet,
 6566             * consectetur adipiscing elit.
 6567             */
 6568        "},
 6569        rust_lang.clone(),
 6570        &mut cx,
 6571    );
 6572
 6573    // different, adjacent comments
 6574    assert_rewrap(
 6575        indoc! {"
 6576            /**
 6577             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6578             */
 6579            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6580            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6581        "},
 6582        indoc! {"
 6583            /**
 6584             *ˇ Lorem ipsum dolor sit amet,
 6585             * consectetur adipiscing elit.
 6586             */
 6587            /*
 6588             *ˇ Lorem ipsum dolor sit amet,
 6589             * consectetur adipiscing elit.
 6590             */
 6591            //ˇ Lorem ipsum dolor sit amet,
 6592            // consectetur adipiscing elit.
 6593        "},
 6594        rust_lang.clone(),
 6595        &mut cx,
 6596    );
 6597
 6598    // selection w/ single short block comment
 6599    assert_rewrap(
 6600        indoc! {"
 6601            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6602        "},
 6603        indoc! {"
 6604            «/*
 6605             * Lorem ipsum dolor sit amet,
 6606             * consectetur adipiscing elit.
 6607             */ˇ»
 6608        "},
 6609        rust_lang.clone(),
 6610        &mut cx,
 6611    );
 6612
 6613    // rewrapping a single comment w/ abutting comments
 6614    assert_rewrap(
 6615        indoc! {"
 6616            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 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            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6625        "},
 6626        rust_lang.clone(),
 6627        &mut cx,
 6628    );
 6629
 6630    // selection w/ non-abutting short block comments
 6631    assert_rewrap(
 6632        indoc! {"
 6633            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6634
 6635            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6636        "},
 6637        indoc! {"
 6638            «/*
 6639             * Lorem ipsum dolor sit amet,
 6640             * consectetur adipiscing elit.
 6641             */
 6642
 6643            /*
 6644             * Lorem ipsum dolor sit amet,
 6645             * consectetur adipiscing elit.
 6646             */ˇ»
 6647        "},
 6648        rust_lang.clone(),
 6649        &mut cx,
 6650    );
 6651
 6652    // selection of multiline block comments
 6653    assert_rewrap(
 6654        indoc! {"
 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             */ˇ»
 6663        "},
 6664        rust_lang.clone(),
 6665        &mut cx,
 6666    );
 6667
 6668    // partial selection of multiline block comments
 6669    assert_rewrap(
 6670        indoc! {"
 6671            «/* Lorem ipsum dolor sit amet,ˇ»
 6672             * consectetur adipiscing elit. */
 6673            /* Lorem ipsum dolor sit amet,
 6674             «* consectetur adipiscing elit. */ˇ»
 6675        "},
 6676        indoc! {"
 6677            «/*
 6678             * Lorem ipsum dolor sit amet,ˇ»
 6679             * consectetur adipiscing elit. */
 6680            /* Lorem ipsum dolor sit amet,
 6681             «* consectetur adipiscing elit.
 6682             */ˇ»
 6683        "},
 6684        rust_lang.clone(),
 6685        &mut cx,
 6686    );
 6687
 6688    // selection w/ abutting short block comments
 6689    // TODO: should not be combined; should rewrap as 2 comments
 6690    assert_rewrap(
 6691        indoc! {"
 6692            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6693            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6694        "},
 6695        // desired behavior:
 6696        // indoc! {"
 6697        //     «/*
 6698        //      * Lorem ipsum dolor sit amet,
 6699        //      * consectetur adipiscing elit.
 6700        //      */
 6701        //     /*
 6702        //      * Lorem ipsum dolor sit amet,
 6703        //      * consectetur adipiscing elit.
 6704        //      */ˇ»
 6705        // "},
 6706        // actual behaviour:
 6707        indoc! {"
 6708            «/*
 6709             * Lorem ipsum dolor sit amet,
 6710             * consectetur adipiscing elit. Lorem
 6711             * ipsum dolor sit amet, consectetur
 6712             * adipiscing elit.
 6713             */ˇ»
 6714        "},
 6715        rust_lang.clone(),
 6716        &mut cx,
 6717    );
 6718
 6719    // TODO: same as above, but with delimiters on separate line
 6720    // assert_rewrap(
 6721    //     indoc! {"
 6722    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6723    //          */
 6724    //         /*
 6725    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6726    //     "},
 6727    //     // desired:
 6728    //     // indoc! {"
 6729    //     //     «/*
 6730    //     //      * Lorem ipsum dolor sit amet,
 6731    //     //      * consectetur adipiscing elit.
 6732    //     //      */
 6733    //     //     /*
 6734    //     //      * Lorem ipsum dolor sit amet,
 6735    //     //      * consectetur adipiscing elit.
 6736    //     //      */ˇ»
 6737    //     // "},
 6738    //     // actual: (but with trailing w/s on the empty lines)
 6739    //     indoc! {"
 6740    //         «/*
 6741    //          * Lorem ipsum dolor sit amet,
 6742    //          * consectetur adipiscing elit.
 6743    //          *
 6744    //          */
 6745    //         /*
 6746    //          *
 6747    //          * Lorem ipsum dolor sit amet,
 6748    //          * consectetur adipiscing elit.
 6749    //          */ˇ»
 6750    //     "},
 6751    //     rust_lang.clone(),
 6752    //     &mut cx,
 6753    // );
 6754
 6755    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6756    assert_rewrap(
 6757        indoc! {"
 6758            /*
 6759             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6760             */
 6761            /*
 6762             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6763            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6764        "},
 6765        // desired:
 6766        // indoc! {"
 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        //      *ˇ Lorem ipsum dolor sit amet
 6777        //      */ /* consectetur adipiscing elit. */
 6778        // "},
 6779        // actual:
 6780        indoc! {"
 6781            /*
 6782             //ˇ Lorem ipsum dolor sit amet,
 6783             // consectetur adipiscing elit.
 6784             */
 6785            /*
 6786             * //ˇ Lorem ipsum dolor sit amet,
 6787             * consectetur adipiscing elit.
 6788             */
 6789            /*
 6790             *ˇ Lorem ipsum dolor sit amet */ /*
 6791             * consectetur adipiscing elit.
 6792             */
 6793        "},
 6794        rust_lang,
 6795        &mut cx,
 6796    );
 6797
 6798    #[track_caller]
 6799    fn assert_rewrap(
 6800        unwrapped_text: &str,
 6801        wrapped_text: &str,
 6802        language: Arc<Language>,
 6803        cx: &mut EditorTestContext,
 6804    ) {
 6805        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6806        cx.set_state(unwrapped_text);
 6807        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6808        cx.assert_editor_state(wrapped_text);
 6809    }
 6810}
 6811
 6812#[gpui::test]
 6813async fn test_hard_wrap(cx: &mut TestAppContext) {
 6814    init_test(cx, |_| {});
 6815    let mut cx = EditorTestContext::new(cx).await;
 6816
 6817    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6818    cx.update_editor(|editor, _, cx| {
 6819        editor.set_hard_wrap(Some(14), cx);
 6820    });
 6821
 6822    cx.set_state(indoc!(
 6823        "
 6824        one two three ˇ
 6825        "
 6826    ));
 6827    cx.simulate_input("four");
 6828    cx.run_until_parked();
 6829
 6830    cx.assert_editor_state(indoc!(
 6831        "
 6832        one two three
 6833        fourˇ
 6834        "
 6835    ));
 6836
 6837    cx.update_editor(|editor, window, cx| {
 6838        editor.newline(&Default::default(), window, cx);
 6839    });
 6840    cx.run_until_parked();
 6841    cx.assert_editor_state(indoc!(
 6842        "
 6843        one two three
 6844        four
 6845        ˇ
 6846        "
 6847    ));
 6848
 6849    cx.simulate_input("five");
 6850    cx.run_until_parked();
 6851    cx.assert_editor_state(indoc!(
 6852        "
 6853        one two three
 6854        four
 6855        fiveˇ
 6856        "
 6857    ));
 6858
 6859    cx.update_editor(|editor, window, cx| {
 6860        editor.newline(&Default::default(), window, cx);
 6861    });
 6862    cx.run_until_parked();
 6863    cx.simulate_input("# ");
 6864    cx.run_until_parked();
 6865    cx.assert_editor_state(indoc!(
 6866        "
 6867        one two three
 6868        four
 6869        five
 6870        # ˇ
 6871        "
 6872    ));
 6873
 6874    cx.update_editor(|editor, window, cx| {
 6875        editor.newline(&Default::default(), window, cx);
 6876    });
 6877    cx.run_until_parked();
 6878    cx.assert_editor_state(indoc!(
 6879        "
 6880        one two three
 6881        four
 6882        five
 6883        #\x20
 6884 6885        "
 6886    ));
 6887
 6888    cx.simulate_input(" 6");
 6889    cx.run_until_parked();
 6890    cx.assert_editor_state(indoc!(
 6891        "
 6892        one two three
 6893        four
 6894        five
 6895        #
 6896        # 6ˇ
 6897        "
 6898    ));
 6899}
 6900
 6901#[gpui::test]
 6902async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6903    init_test(cx, |_| {});
 6904
 6905    let mut cx = EditorTestContext::new(cx).await;
 6906
 6907    cx.set_state(indoc! {"The quick brownˇ"});
 6908    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6909    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6910
 6911    cx.set_state(indoc! {"The emacs foxˇ"});
 6912    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6913    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 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| e.cut(&Cut, window, cx));
 6920    cx.assert_editor_state(indoc! {"
 6921        The quickˇ
 6922        ˇthe lazy dog"});
 6923
 6924    cx.set_state(indoc! {"
 6925        The quick« brownˇ»
 6926        fox jumps overˇ
 6927        the lazy dog"});
 6928    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6929    cx.assert_editor_state(indoc! {"
 6930        The quickˇ
 6931        fox jumps overˇ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| {
 6938        e.cut_to_end_of_line(
 6939            &CutToEndOfLine {
 6940                stop_at_newlines: true,
 6941            },
 6942            window,
 6943            cx,
 6944        )
 6945    });
 6946    cx.assert_editor_state(indoc! {"
 6947        The quickˇ
 6948        fox jumps overˇ
 6949        the lazy dog"});
 6950
 6951    cx.set_state(indoc! {"
 6952        The quick« brownˇ»
 6953        fox jumps overˇ
 6954        the lazy dog"});
 6955    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6956    cx.assert_editor_state(indoc! {"
 6957        The quickˇ
 6958        fox jumps overˇthe lazy dog"});
 6959}
 6960
 6961#[gpui::test]
 6962async fn test_clipboard(cx: &mut TestAppContext) {
 6963    init_test(cx, |_| {});
 6964
 6965    let mut cx = EditorTestContext::new(cx).await;
 6966
 6967    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6968    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6969    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6970
 6971    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6972    cx.set_state("two ˇfour ˇsix ˇ");
 6973    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6974    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6975
 6976    // Paste again but with only two cursors. Since the number of cursors doesn't
 6977    // match the number of slices in the clipboard, the entire clipboard text
 6978    // is pasted at each cursor.
 6979    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6980    cx.update_editor(|e, window, cx| {
 6981        e.handle_input("( ", window, cx);
 6982        e.paste(&Paste, window, cx);
 6983        e.handle_input(") ", window, cx);
 6984    });
 6985    cx.assert_editor_state(
 6986        &([
 6987            "( one✅ ",
 6988            "three ",
 6989            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6990            "three ",
 6991            "five ) ˇ",
 6992        ]
 6993        .join("\n")),
 6994    );
 6995
 6996    // Cut with three selections, one of which is full-line.
 6997    cx.set_state(indoc! {"
 6998        1«2ˇ»3
 6999        4ˇ567
 7000        «8ˇ»9"});
 7001    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7002    cx.assert_editor_state(indoc! {"
 7003        1ˇ3
 7004        ˇ9"});
 7005
 7006    // Paste with three selections, noticing how the copied selection that was full-line
 7007    // gets inserted before the second cursor.
 7008    cx.set_state(indoc! {"
 7009        1ˇ3
 7010 7011        «oˇ»ne"});
 7012    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7013    cx.assert_editor_state(indoc! {"
 7014        12ˇ3
 7015        4567
 7016 7017        8ˇne"});
 7018
 7019    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7020    cx.set_state(indoc! {"
 7021        The quick brown
 7022        fox juˇmps over
 7023        the lazy dog"});
 7024    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7025    assert_eq!(
 7026        cx.read_from_clipboard()
 7027            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7028        Some("fox jumps over\n".to_string())
 7029    );
 7030
 7031    // Paste with three selections, noticing how the copied full-line selection is inserted
 7032    // before the empty selections but replaces the selection that is non-empty.
 7033    cx.set_state(indoc! {"
 7034        Tˇhe quick brown
 7035        «foˇ»x jumps over
 7036        tˇhe lazy dog"});
 7037    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7038    cx.assert_editor_state(indoc! {"
 7039        fox jumps over
 7040        Tˇhe quick brown
 7041        fox jumps over
 7042        ˇx jumps over
 7043        fox jumps over
 7044        tˇhe lazy dog"});
 7045}
 7046
 7047#[gpui::test]
 7048async fn test_copy_trim(cx: &mut TestAppContext) {
 7049    init_test(cx, |_| {});
 7050
 7051    let mut cx = EditorTestContext::new(cx).await;
 7052    cx.set_state(
 7053        r#"            «for selection in selections.iter() {
 7054            let mut start = selection.start;
 7055            let mut end = selection.end;
 7056            let is_entire_line = selection.is_empty();
 7057            if is_entire_line {
 7058                start = Point::new(start.row, 0);ˇ»
 7059                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7060            }
 7061        "#,
 7062    );
 7063    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7064    assert_eq!(
 7065        cx.read_from_clipboard()
 7066            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7067        Some(
 7068            "for selection in selections.iter() {
 7069            let mut start = selection.start;
 7070            let mut end = selection.end;
 7071            let is_entire_line = selection.is_empty();
 7072            if is_entire_line {
 7073                start = Point::new(start.row, 0);"
 7074                .to_string()
 7075        ),
 7076        "Regular copying preserves all indentation selected",
 7077    );
 7078    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7079    assert_eq!(
 7080        cx.read_from_clipboard()
 7081            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7082        Some(
 7083            "for selection in selections.iter() {
 7084let mut start = selection.start;
 7085let mut end = selection.end;
 7086let is_entire_line = selection.is_empty();
 7087if is_entire_line {
 7088    start = Point::new(start.row, 0);"
 7089                .to_string()
 7090        ),
 7091        "Copying with stripping should strip all leading whitespaces"
 7092    );
 7093
 7094    cx.set_state(
 7095        r#"       «     for selection in selections.iter() {
 7096            let mut start = selection.start;
 7097            let mut end = selection.end;
 7098            let is_entire_line = selection.is_empty();
 7099            if is_entire_line {
 7100                start = Point::new(start.row, 0);ˇ»
 7101                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7102            }
 7103        "#,
 7104    );
 7105    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7106    assert_eq!(
 7107        cx.read_from_clipboard()
 7108            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7109        Some(
 7110            "     for selection in selections.iter() {
 7111            let mut start = selection.start;
 7112            let mut end = selection.end;
 7113            let is_entire_line = selection.is_empty();
 7114            if is_entire_line {
 7115                start = Point::new(start.row, 0);"
 7116                .to_string()
 7117        ),
 7118        "Regular copying preserves all indentation selected",
 7119    );
 7120    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7121    assert_eq!(
 7122        cx.read_from_clipboard()
 7123            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7124        Some(
 7125            "for selection in selections.iter() {
 7126let mut start = selection.start;
 7127let mut end = selection.end;
 7128let is_entire_line = selection.is_empty();
 7129if is_entire_line {
 7130    start = Point::new(start.row, 0);"
 7131                .to_string()
 7132        ),
 7133        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7134    );
 7135
 7136    cx.set_state(
 7137        r#"       «ˇ     for selection in selections.iter() {
 7138            let mut start = selection.start;
 7139            let mut end = selection.end;
 7140            let is_entire_line = selection.is_empty();
 7141            if is_entire_line {
 7142                start = Point::new(start.row, 0);»
 7143                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7144            }
 7145        "#,
 7146    );
 7147    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7148    assert_eq!(
 7149        cx.read_from_clipboard()
 7150            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7151        Some(
 7152            "     for selection in selections.iter() {
 7153            let mut start = selection.start;
 7154            let mut end = selection.end;
 7155            let is_entire_line = selection.is_empty();
 7156            if is_entire_line {
 7157                start = Point::new(start.row, 0);"
 7158                .to_string()
 7159        ),
 7160        "Regular copying for reverse selection works the same",
 7161    );
 7162    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7163    assert_eq!(
 7164        cx.read_from_clipboard()
 7165            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7166        Some(
 7167            "for selection in selections.iter() {
 7168let mut start = selection.start;
 7169let mut end = selection.end;
 7170let is_entire_line = selection.is_empty();
 7171if is_entire_line {
 7172    start = Point::new(start.row, 0);"
 7173                .to_string()
 7174        ),
 7175        "Copying with stripping for reverse selection works the same"
 7176    );
 7177
 7178    cx.set_state(
 7179        r#"            for selection «in selections.iter() {
 7180            let mut start = selection.start;
 7181            let mut end = selection.end;
 7182            let is_entire_line = selection.is_empty();
 7183            if is_entire_line {
 7184                start = Point::new(start.row, 0);ˇ»
 7185                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7186            }
 7187        "#,
 7188    );
 7189    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7190    assert_eq!(
 7191        cx.read_from_clipboard()
 7192            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7193        Some(
 7194            "in selections.iter() {
 7195            let mut start = selection.start;
 7196            let mut end = selection.end;
 7197            let is_entire_line = selection.is_empty();
 7198            if is_entire_line {
 7199                start = Point::new(start.row, 0);"
 7200                .to_string()
 7201        ),
 7202        "When selecting past the indent, the copying works as usual",
 7203    );
 7204    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7205    assert_eq!(
 7206        cx.read_from_clipboard()
 7207            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7208        Some(
 7209            "in selections.iter() {
 7210            let mut start = selection.start;
 7211            let mut end = selection.end;
 7212            let is_entire_line = selection.is_empty();
 7213            if is_entire_line {
 7214                start = Point::new(start.row, 0);"
 7215                .to_string()
 7216        ),
 7217        "When selecting past the indent, nothing is trimmed"
 7218    );
 7219
 7220    cx.set_state(
 7221        r#"            «for selection in selections.iter() {
 7222            let mut start = selection.start;
 7223
 7224            let mut end = selection.end;
 7225            let is_entire_line = selection.is_empty();
 7226            if is_entire_line {
 7227                start = Point::new(start.row, 0);
 7228ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7229            }
 7230        "#,
 7231    );
 7232    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7233    assert_eq!(
 7234        cx.read_from_clipboard()
 7235            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7236        Some(
 7237            "for selection in selections.iter() {
 7238let mut start = selection.start;
 7239
 7240let mut end = selection.end;
 7241let is_entire_line = selection.is_empty();
 7242if is_entire_line {
 7243    start = Point::new(start.row, 0);
 7244"
 7245            .to_string()
 7246        ),
 7247        "Copying with stripping should ignore empty lines"
 7248    );
 7249}
 7250
 7251#[gpui::test]
 7252async fn test_paste_multiline(cx: &mut TestAppContext) {
 7253    init_test(cx, |_| {});
 7254
 7255    let mut cx = EditorTestContext::new(cx).await;
 7256    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7257
 7258    // Cut an indented block, without the leading whitespace.
 7259    cx.set_state(indoc! {"
 7260        const a: B = (
 7261            c(),
 7262            «d(
 7263                e,
 7264                f
 7265            )ˇ»
 7266        );
 7267    "});
 7268    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7269    cx.assert_editor_state(indoc! {"
 7270        const a: B = (
 7271            c(),
 7272            ˇ
 7273        );
 7274    "});
 7275
 7276    // Paste it at the same position.
 7277    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7278    cx.assert_editor_state(indoc! {"
 7279        const a: B = (
 7280            c(),
 7281            d(
 7282                e,
 7283                f
 7284 7285        );
 7286    "});
 7287
 7288    // Paste it at a line with a lower indent level.
 7289    cx.set_state(indoc! {"
 7290        ˇ
 7291        const a: B = (
 7292            c(),
 7293        );
 7294    "});
 7295    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7296    cx.assert_editor_state(indoc! {"
 7297        d(
 7298            e,
 7299            f
 7300 7301        const a: B = (
 7302            c(),
 7303        );
 7304    "});
 7305
 7306    // Cut an indented block, with the leading whitespace.
 7307    cx.set_state(indoc! {"
 7308        const a: B = (
 7309            c(),
 7310        «    d(
 7311                e,
 7312                f
 7313            )
 7314        ˇ»);
 7315    "});
 7316    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7317    cx.assert_editor_state(indoc! {"
 7318        const a: B = (
 7319            c(),
 7320        ˇ);
 7321    "});
 7322
 7323    // Paste it at the same position.
 7324    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7325    cx.assert_editor_state(indoc! {"
 7326        const a: B = (
 7327            c(),
 7328            d(
 7329                e,
 7330                f
 7331            )
 7332        ˇ);
 7333    "});
 7334
 7335    // Paste it at a line with a higher indent level.
 7336    cx.set_state(indoc! {"
 7337        const a: B = (
 7338            c(),
 7339            d(
 7340                e,
 7341 7342            )
 7343        );
 7344    "});
 7345    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7346    cx.assert_editor_state(indoc! {"
 7347        const a: B = (
 7348            c(),
 7349            d(
 7350                e,
 7351                f    d(
 7352                    e,
 7353                    f
 7354                )
 7355        ˇ
 7356            )
 7357        );
 7358    "});
 7359
 7360    // Copy an indented block, starting mid-line
 7361    cx.set_state(indoc! {"
 7362        const a: B = (
 7363            c(),
 7364            somethin«g(
 7365                e,
 7366                f
 7367            )ˇ»
 7368        );
 7369    "});
 7370    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7371
 7372    // Paste it on a line with a lower indent level
 7373    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7374    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7375    cx.assert_editor_state(indoc! {"
 7376        const a: B = (
 7377            c(),
 7378            something(
 7379                e,
 7380                f
 7381            )
 7382        );
 7383        g(
 7384            e,
 7385            f
 7386"});
 7387}
 7388
 7389#[gpui::test]
 7390async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7391    init_test(cx, |_| {});
 7392
 7393    cx.write_to_clipboard(ClipboardItem::new_string(
 7394        "    d(\n        e\n    );\n".into(),
 7395    ));
 7396
 7397    let mut cx = EditorTestContext::new(cx).await;
 7398    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7399
 7400    cx.set_state(indoc! {"
 7401        fn a() {
 7402            b();
 7403            if c() {
 7404                ˇ
 7405            }
 7406        }
 7407    "});
 7408
 7409    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7410    cx.assert_editor_state(indoc! {"
 7411        fn a() {
 7412            b();
 7413            if c() {
 7414                d(
 7415                    e
 7416                );
 7417        ˇ
 7418            }
 7419        }
 7420    "});
 7421
 7422    cx.set_state(indoc! {"
 7423        fn a() {
 7424            b();
 7425            ˇ
 7426        }
 7427    "});
 7428
 7429    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7430    cx.assert_editor_state(indoc! {"
 7431        fn a() {
 7432            b();
 7433            d(
 7434                e
 7435            );
 7436        ˇ
 7437        }
 7438    "});
 7439}
 7440
 7441#[gpui::test]
 7442fn test_select_all(cx: &mut TestAppContext) {
 7443    init_test(cx, |_| {});
 7444
 7445    let editor = cx.add_window(|window, cx| {
 7446        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7447        build_editor(buffer, window, cx)
 7448    });
 7449    _ = editor.update(cx, |editor, window, cx| {
 7450        editor.select_all(&SelectAll, window, cx);
 7451        assert_eq!(
 7452            editor.selections.display_ranges(cx),
 7453            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7454        );
 7455    });
 7456}
 7457
 7458#[gpui::test]
 7459fn test_select_line(cx: &mut TestAppContext) {
 7460    init_test(cx, |_| {});
 7461
 7462    let editor = cx.add_window(|window, cx| {
 7463        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7464        build_editor(buffer, window, cx)
 7465    });
 7466    _ = editor.update(cx, |editor, window, cx| {
 7467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7468            s.select_display_ranges([
 7469                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7470                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7471                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7472                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7473            ])
 7474        });
 7475        editor.select_line(&SelectLine, window, cx);
 7476        assert_eq!(
 7477            editor.selections.display_ranges(cx),
 7478            vec![
 7479                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7480                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7481            ]
 7482        );
 7483    });
 7484
 7485    _ = editor.update(cx, |editor, window, cx| {
 7486        editor.select_line(&SelectLine, window, cx);
 7487        assert_eq!(
 7488            editor.selections.display_ranges(cx),
 7489            vec![
 7490                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7491                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7492            ]
 7493        );
 7494    });
 7495
 7496    _ = editor.update(cx, |editor, window, cx| {
 7497        editor.select_line(&SelectLine, window, cx);
 7498        assert_eq!(
 7499            editor.selections.display_ranges(cx),
 7500            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7501        );
 7502    });
 7503}
 7504
 7505#[gpui::test]
 7506async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7507    init_test(cx, |_| {});
 7508    let mut cx = EditorTestContext::new(cx).await;
 7509
 7510    #[track_caller]
 7511    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7512        cx.set_state(initial_state);
 7513        cx.update_editor(|e, window, cx| {
 7514            e.split_selection_into_lines(&Default::default(), window, cx)
 7515        });
 7516        cx.assert_editor_state(expected_state);
 7517    }
 7518
 7519    // Selection starts and ends at the middle of lines, left-to-right
 7520    test(
 7521        &mut cx,
 7522        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7523        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7524    );
 7525    // Same thing, right-to-left
 7526    test(
 7527        &mut cx,
 7528        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7529        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7530    );
 7531
 7532    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7533    test(
 7534        &mut cx,
 7535        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7536        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7537    );
 7538    // Same thing, right-to-left
 7539    test(
 7540        &mut cx,
 7541        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7542        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7543    );
 7544
 7545    // Whole buffer, left-to-right, last line ends with newline
 7546    test(
 7547        &mut cx,
 7548        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7549        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7550    );
 7551    // Same thing, right-to-left
 7552    test(
 7553        &mut cx,
 7554        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7555        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7556    );
 7557
 7558    // Starts at the end of a line, ends at the start of another
 7559    test(
 7560        &mut cx,
 7561        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7562        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7563    );
 7564}
 7565
 7566#[gpui::test]
 7567async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7568    init_test(cx, |_| {});
 7569
 7570    let editor = cx.add_window(|window, cx| {
 7571        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7572        build_editor(buffer, window, cx)
 7573    });
 7574
 7575    // setup
 7576    _ = editor.update(cx, |editor, window, cx| {
 7577        editor.fold_creases(
 7578            vec![
 7579                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7580                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7581                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7582            ],
 7583            true,
 7584            window,
 7585            cx,
 7586        );
 7587        assert_eq!(
 7588            editor.display_text(cx),
 7589            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7590        );
 7591    });
 7592
 7593    _ = editor.update(cx, |editor, window, cx| {
 7594        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7595            s.select_display_ranges([
 7596                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7597                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7598                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7599                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7600            ])
 7601        });
 7602        editor.split_selection_into_lines(&Default::default(), window, cx);
 7603        assert_eq!(
 7604            editor.display_text(cx),
 7605            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7606        );
 7607    });
 7608    EditorTestContext::for_editor(editor, cx)
 7609        .await
 7610        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7611
 7612    _ = editor.update(cx, |editor, window, cx| {
 7613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7614            s.select_display_ranges([
 7615                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7616            ])
 7617        });
 7618        editor.split_selection_into_lines(&Default::default(), window, cx);
 7619        assert_eq!(
 7620            editor.display_text(cx),
 7621            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7622        );
 7623        assert_eq!(
 7624            editor.selections.display_ranges(cx),
 7625            [
 7626                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7627                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7628                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7629                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7630                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7631                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7632                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7633            ]
 7634        );
 7635    });
 7636    EditorTestContext::for_editor(editor, cx)
 7637        .await
 7638        .assert_editor_state(
 7639            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7640        );
 7641}
 7642
 7643#[gpui::test]
 7644async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7645    init_test(cx, |_| {});
 7646
 7647    let mut cx = EditorTestContext::new(cx).await;
 7648
 7649    cx.set_state(indoc!(
 7650        r#"abc
 7651           defˇghi
 7652
 7653           jk
 7654           nlmo
 7655           "#
 7656    ));
 7657
 7658    cx.update_editor(|editor, window, cx| {
 7659        editor.add_selection_above(&Default::default(), window, cx);
 7660    });
 7661
 7662    cx.assert_editor_state(indoc!(
 7663        r#"abcˇ
 7664           defˇghi
 7665
 7666           jk
 7667           nlmo
 7668           "#
 7669    ));
 7670
 7671    cx.update_editor(|editor, window, cx| {
 7672        editor.add_selection_above(&Default::default(), window, cx);
 7673    });
 7674
 7675    cx.assert_editor_state(indoc!(
 7676        r#"abcˇ
 7677            defˇghi
 7678
 7679            jk
 7680            nlmo
 7681            "#
 7682    ));
 7683
 7684    cx.update_editor(|editor, window, cx| {
 7685        editor.add_selection_below(&Default::default(), window, cx);
 7686    });
 7687
 7688    cx.assert_editor_state(indoc!(
 7689        r#"abc
 7690           defˇghi
 7691
 7692           jk
 7693           nlmo
 7694           "#
 7695    ));
 7696
 7697    cx.update_editor(|editor, window, cx| {
 7698        editor.undo_selection(&Default::default(), window, cx);
 7699    });
 7700
 7701    cx.assert_editor_state(indoc!(
 7702        r#"abcˇ
 7703           defˇghi
 7704
 7705           jk
 7706           nlmo
 7707           "#
 7708    ));
 7709
 7710    cx.update_editor(|editor, window, cx| {
 7711        editor.redo_selection(&Default::default(), window, cx);
 7712    });
 7713
 7714    cx.assert_editor_state(indoc!(
 7715        r#"abc
 7716           defˇghi
 7717
 7718           jk
 7719           nlmo
 7720           "#
 7721    ));
 7722
 7723    cx.update_editor(|editor, window, cx| {
 7724        editor.add_selection_below(&Default::default(), window, cx);
 7725    });
 7726
 7727    cx.assert_editor_state(indoc!(
 7728        r#"abc
 7729           defˇghi
 7730           ˇ
 7731           jk
 7732           nlmo
 7733           "#
 7734    ));
 7735
 7736    cx.update_editor(|editor, window, cx| {
 7737        editor.add_selection_below(&Default::default(), window, cx);
 7738    });
 7739
 7740    cx.assert_editor_state(indoc!(
 7741        r#"abc
 7742           defˇghi
 7743           ˇ
 7744           jkˇ
 7745           nlmo
 7746           "#
 7747    ));
 7748
 7749    cx.update_editor(|editor, window, cx| {
 7750        editor.add_selection_below(&Default::default(), window, cx);
 7751    });
 7752
 7753    cx.assert_editor_state(indoc!(
 7754        r#"abc
 7755           defˇghi
 7756           ˇ
 7757           jkˇ
 7758           nlmˇo
 7759           "#
 7760    ));
 7761
 7762    cx.update_editor(|editor, window, cx| {
 7763        editor.add_selection_below(&Default::default(), window, cx);
 7764    });
 7765
 7766    cx.assert_editor_state(indoc!(
 7767        r#"abc
 7768           defˇghi
 7769           ˇ
 7770           jkˇ
 7771           nlmˇo
 7772           ˇ"#
 7773    ));
 7774
 7775    // change selections
 7776    cx.set_state(indoc!(
 7777        r#"abc
 7778           def«ˇg»hi
 7779
 7780           jk
 7781           nlmo
 7782           "#
 7783    ));
 7784
 7785    cx.update_editor(|editor, window, cx| {
 7786        editor.add_selection_below(&Default::default(), window, cx);
 7787    });
 7788
 7789    cx.assert_editor_state(indoc!(
 7790        r#"abc
 7791           def«ˇg»hi
 7792
 7793           jk
 7794           nlm«ˇo»
 7795           "#
 7796    ));
 7797
 7798    cx.update_editor(|editor, window, cx| {
 7799        editor.add_selection_below(&Default::default(), window, cx);
 7800    });
 7801
 7802    cx.assert_editor_state(indoc!(
 7803        r#"abc
 7804           def«ˇg»hi
 7805
 7806           jk
 7807           nlm«ˇo»
 7808           "#
 7809    ));
 7810
 7811    cx.update_editor(|editor, window, cx| {
 7812        editor.add_selection_above(&Default::default(), window, cx);
 7813    });
 7814
 7815    cx.assert_editor_state(indoc!(
 7816        r#"abc
 7817           def«ˇg»hi
 7818
 7819           jk
 7820           nlmo
 7821           "#
 7822    ));
 7823
 7824    cx.update_editor(|editor, window, cx| {
 7825        editor.add_selection_above(&Default::default(), window, cx);
 7826    });
 7827
 7828    cx.assert_editor_state(indoc!(
 7829        r#"abc
 7830           def«ˇg»hi
 7831
 7832           jk
 7833           nlmo
 7834           "#
 7835    ));
 7836
 7837    // Change selections again
 7838    cx.set_state(indoc!(
 7839        r#"a«bc
 7840           defgˇ»hi
 7841
 7842           jk
 7843           nlmo
 7844           "#
 7845    ));
 7846
 7847    cx.update_editor(|editor, window, cx| {
 7848        editor.add_selection_below(&Default::default(), window, cx);
 7849    });
 7850
 7851    cx.assert_editor_state(indoc!(
 7852        r#"a«bcˇ»
 7853           d«efgˇ»hi
 7854
 7855           j«kˇ»
 7856           nlmo
 7857           "#
 7858    ));
 7859
 7860    cx.update_editor(|editor, window, cx| {
 7861        editor.add_selection_below(&Default::default(), window, cx);
 7862    });
 7863    cx.assert_editor_state(indoc!(
 7864        r#"a«bcˇ»
 7865           d«efgˇ»hi
 7866
 7867           j«kˇ»
 7868           n«lmoˇ»
 7869           "#
 7870    ));
 7871    cx.update_editor(|editor, window, cx| {
 7872        editor.add_selection_above(&Default::default(), window, cx);
 7873    });
 7874
 7875    cx.assert_editor_state(indoc!(
 7876        r#"a«bcˇ»
 7877           d«efgˇ»hi
 7878
 7879           j«kˇ»
 7880           nlmo
 7881           "#
 7882    ));
 7883
 7884    // Change selections again
 7885    cx.set_state(indoc!(
 7886        r#"abc
 7887           d«ˇefghi
 7888
 7889           jk
 7890           nlm»o
 7891           "#
 7892    ));
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.add_selection_above(&Default::default(), window, cx);
 7896    });
 7897
 7898    cx.assert_editor_state(indoc!(
 7899        r#"a«ˇbc»
 7900           d«ˇef»ghi
 7901
 7902           j«ˇk»
 7903           n«ˇlm»o
 7904           "#
 7905    ));
 7906
 7907    cx.update_editor(|editor, window, cx| {
 7908        editor.add_selection_below(&Default::default(), window, cx);
 7909    });
 7910
 7911    cx.assert_editor_state(indoc!(
 7912        r#"abc
 7913           d«ˇef»ghi
 7914
 7915           j«ˇk»
 7916           n«ˇlm»o
 7917           "#
 7918    ));
 7919}
 7920
 7921#[gpui::test]
 7922async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7923    init_test(cx, |_| {});
 7924    let mut cx = EditorTestContext::new(cx).await;
 7925
 7926    cx.set_state(indoc!(
 7927        r#"line onˇe
 7928           liˇne two
 7929           line three
 7930           line four"#
 7931    ));
 7932
 7933    cx.update_editor(|editor, window, cx| {
 7934        editor.add_selection_below(&Default::default(), window, cx);
 7935    });
 7936
 7937    // test multiple cursors expand in the same direction
 7938    cx.assert_editor_state(indoc!(
 7939        r#"line onˇe
 7940           liˇne twˇo
 7941           liˇne three
 7942           line four"#
 7943    ));
 7944
 7945    cx.update_editor(|editor, window, cx| {
 7946        editor.add_selection_below(&Default::default(), window, cx);
 7947    });
 7948
 7949    cx.update_editor(|editor, window, cx| {
 7950        editor.add_selection_below(&Default::default(), window, cx);
 7951    });
 7952
 7953    // test multiple cursors expand below overflow
 7954    cx.assert_editor_state(indoc!(
 7955        r#"line onˇe
 7956           liˇne twˇo
 7957           liˇne thˇree
 7958           liˇne foˇur"#
 7959    ));
 7960
 7961    cx.update_editor(|editor, window, cx| {
 7962        editor.add_selection_above(&Default::default(), window, cx);
 7963    });
 7964
 7965    // test multiple cursors retrieves back correctly
 7966    cx.assert_editor_state(indoc!(
 7967        r#"line onˇe
 7968           liˇne twˇo
 7969           liˇne thˇree
 7970           line four"#
 7971    ));
 7972
 7973    cx.update_editor(|editor, window, cx| {
 7974        editor.add_selection_above(&Default::default(), window, cx);
 7975    });
 7976
 7977    cx.update_editor(|editor, window, cx| {
 7978        editor.add_selection_above(&Default::default(), window, cx);
 7979    });
 7980
 7981    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7982    cx.assert_editor_state(indoc!(
 7983        r#"liˇne onˇe
 7984           liˇne two
 7985           line three
 7986           line four"#
 7987    ));
 7988
 7989    cx.update_editor(|editor, window, cx| {
 7990        editor.undo_selection(&Default::default(), window, cx);
 7991    });
 7992
 7993    // test undo
 7994    cx.assert_editor_state(indoc!(
 7995        r#"line onˇe
 7996           liˇne twˇo
 7997           line three
 7998           line four"#
 7999    ));
 8000
 8001    cx.update_editor(|editor, window, cx| {
 8002        editor.redo_selection(&Default::default(), window, cx);
 8003    });
 8004
 8005    // test redo
 8006    cx.assert_editor_state(indoc!(
 8007        r#"liˇne onˇe
 8008           liˇne two
 8009           line three
 8010           line four"#
 8011    ));
 8012
 8013    cx.set_state(indoc!(
 8014        r#"abcd
 8015           ef«ghˇ»
 8016           ijkl
 8017           «mˇ»nop"#
 8018    ));
 8019
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.add_selection_above(&Default::default(), window, cx);
 8022    });
 8023
 8024    // test multiple selections expand in the same direction
 8025    cx.assert_editor_state(indoc!(
 8026        r#"ab«cdˇ»
 8027           ef«ghˇ»
 8028           «iˇ»jkl
 8029           «mˇ»nop"#
 8030    ));
 8031
 8032    cx.update_editor(|editor, window, cx| {
 8033        editor.add_selection_above(&Default::default(), window, cx);
 8034    });
 8035
 8036    // test multiple selection upward overflow
 8037    cx.assert_editor_state(indoc!(
 8038        r#"ab«cdˇ»
 8039           «eˇ»f«ghˇ»
 8040           «iˇ»jkl
 8041           «mˇ»nop"#
 8042    ));
 8043
 8044    cx.update_editor(|editor, window, cx| {
 8045        editor.add_selection_below(&Default::default(), window, cx);
 8046    });
 8047
 8048    // test multiple selection retrieves back correctly
 8049    cx.assert_editor_state(indoc!(
 8050        r#"abcd
 8051           ef«ghˇ»
 8052           «iˇ»jkl
 8053           «mˇ»nop"#
 8054    ));
 8055
 8056    cx.update_editor(|editor, window, cx| {
 8057        editor.add_selection_below(&Default::default(), window, cx);
 8058    });
 8059
 8060    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8061    cx.assert_editor_state(indoc!(
 8062        r#"abcd
 8063           ef«ghˇ»
 8064           ij«klˇ»
 8065           «mˇ»nop"#
 8066    ));
 8067
 8068    cx.update_editor(|editor, window, cx| {
 8069        editor.undo_selection(&Default::default(), window, cx);
 8070    });
 8071
 8072    // test undo
 8073    cx.assert_editor_state(indoc!(
 8074        r#"abcd
 8075           ef«ghˇ»
 8076           «iˇ»jkl
 8077           «mˇ»nop"#
 8078    ));
 8079
 8080    cx.update_editor(|editor, window, cx| {
 8081        editor.redo_selection(&Default::default(), window, cx);
 8082    });
 8083
 8084    // test redo
 8085    cx.assert_editor_state(indoc!(
 8086        r#"abcd
 8087           ef«ghˇ»
 8088           ij«klˇ»
 8089           «mˇ»nop"#
 8090    ));
 8091}
 8092
 8093#[gpui::test]
 8094async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8095    init_test(cx, |_| {});
 8096    let mut cx = EditorTestContext::new(cx).await;
 8097
 8098    cx.set_state(indoc!(
 8099        r#"line onˇe
 8100           liˇne two
 8101           line three
 8102           line four"#
 8103    ));
 8104
 8105    cx.update_editor(|editor, window, cx| {
 8106        editor.add_selection_below(&Default::default(), window, cx);
 8107        editor.add_selection_below(&Default::default(), window, cx);
 8108        editor.add_selection_below(&Default::default(), window, cx);
 8109    });
 8110
 8111    // initial state with two multi cursor groups
 8112    cx.assert_editor_state(indoc!(
 8113        r#"line onˇe
 8114           liˇne twˇo
 8115           liˇne thˇree
 8116           liˇne foˇur"#
 8117    ));
 8118
 8119    // add single cursor in middle - simulate opt click
 8120    cx.update_editor(|editor, window, cx| {
 8121        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8122        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8123        editor.end_selection(window, cx);
 8124    });
 8125
 8126    cx.assert_editor_state(indoc!(
 8127        r#"line onˇe
 8128           liˇne twˇo
 8129           liˇneˇ thˇree
 8130           liˇne foˇur"#
 8131    ));
 8132
 8133    cx.update_editor(|editor, window, cx| {
 8134        editor.add_selection_above(&Default::default(), window, cx);
 8135    });
 8136
 8137    // test new added selection expands above and existing selection shrinks
 8138    cx.assert_editor_state(indoc!(
 8139        r#"line onˇe
 8140           liˇneˇ twˇo
 8141           liˇneˇ thˇree
 8142           line four"#
 8143    ));
 8144
 8145    cx.update_editor(|editor, window, cx| {
 8146        editor.add_selection_above(&Default::default(), window, cx);
 8147    });
 8148
 8149    // test new added selection expands above and existing selection shrinks
 8150    cx.assert_editor_state(indoc!(
 8151        r#"lineˇ onˇe
 8152           liˇneˇ twˇo
 8153           lineˇ three
 8154           line four"#
 8155    ));
 8156
 8157    // intial state with two selection groups
 8158    cx.set_state(indoc!(
 8159        r#"abcd
 8160           ef«ghˇ»
 8161           ijkl
 8162           «mˇ»nop"#
 8163    ));
 8164
 8165    cx.update_editor(|editor, window, cx| {
 8166        editor.add_selection_above(&Default::default(), window, cx);
 8167        editor.add_selection_above(&Default::default(), window, cx);
 8168    });
 8169
 8170    cx.assert_editor_state(indoc!(
 8171        r#"ab«cdˇ»
 8172           «eˇ»f«ghˇ»
 8173           «iˇ»jkl
 8174           «mˇ»nop"#
 8175    ));
 8176
 8177    // add single selection in middle - simulate opt drag
 8178    cx.update_editor(|editor, window, cx| {
 8179        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8180        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8181        editor.update_selection(
 8182            DisplayPoint::new(DisplayRow(2), 4),
 8183            0,
 8184            gpui::Point::<f32>::default(),
 8185            window,
 8186            cx,
 8187        );
 8188        editor.end_selection(window, cx);
 8189    });
 8190
 8191    cx.assert_editor_state(indoc!(
 8192        r#"ab«cdˇ»
 8193           «eˇ»f«ghˇ»
 8194           «iˇ»jk«lˇ»
 8195           «mˇ»nop"#
 8196    ));
 8197
 8198    cx.update_editor(|editor, window, cx| {
 8199        editor.add_selection_below(&Default::default(), window, cx);
 8200    });
 8201
 8202    // test new added selection expands below, others shrinks from above
 8203    cx.assert_editor_state(indoc!(
 8204        r#"abcd
 8205           ef«ghˇ»
 8206           «iˇ»jk«lˇ»
 8207           «mˇ»no«pˇ»"#
 8208    ));
 8209}
 8210
 8211#[gpui::test]
 8212async fn test_select_next(cx: &mut TestAppContext) {
 8213    init_test(cx, |_| {});
 8214
 8215    let mut cx = EditorTestContext::new(cx).await;
 8216    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 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\nabc");
 8221
 8222    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8223        .unwrap();
 8224    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8225
 8226    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8227    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8228
 8229    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8230    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8231
 8232    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8233        .unwrap();
 8234    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8235
 8236    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8237        .unwrap();
 8238    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8239
 8240    // Test selection direction should be preserved
 8241    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8242
 8243    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8244        .unwrap();
 8245    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8246}
 8247
 8248#[gpui::test]
 8249async fn test_select_all_matches(cx: &mut TestAppContext) {
 8250    init_test(cx, |_| {});
 8251
 8252    let mut cx = EditorTestContext::new(cx).await;
 8253
 8254    // Test caret-only selections
 8255    cx.set_state("abc\nˇabc abc\ndefabc\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ˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8259
 8260    // Test left-to-right selections
 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ˇ»\n«abcˇ»");
 8265
 8266    // Test right-to-left selections
 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»\n«ˇabc»");
 8271
 8272    // Test selecting whitespace with caret selection
 8273    cx.set_state("abc\nˇ   abc\nabc");
 8274    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8275        .unwrap();
 8276    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8277
 8278    // Test selecting whitespace with left-to-right selection
 8279    cx.set_state("abc\n«ˇ  »abc\nabc");
 8280    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8281        .unwrap();
 8282    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8283
 8284    // Test no matches with right-to-left selection
 8285    cx.set_state("abc\n«  ˇ»abc\nabc");
 8286    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8287        .unwrap();
 8288    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8289
 8290    // Test with a single word and clip_at_line_ends=true (#29823)
 8291    cx.set_state("aˇbc");
 8292    cx.update_editor(|e, window, cx| {
 8293        e.set_clip_at_line_ends(true, cx);
 8294        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8295        e.set_clip_at_line_ends(false, cx);
 8296    });
 8297    cx.assert_editor_state("«abcˇ»");
 8298}
 8299
 8300#[gpui::test]
 8301async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8302    init_test(cx, |_| {});
 8303
 8304    let mut cx = EditorTestContext::new(cx).await;
 8305
 8306    let large_body_1 = "\nd".repeat(200);
 8307    let large_body_2 = "\ne".repeat(200);
 8308
 8309    cx.set_state(&format!(
 8310        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8311    ));
 8312    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8313        let scroll_position = editor.scroll_position(cx);
 8314        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8315        scroll_position
 8316    });
 8317
 8318    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8319        .unwrap();
 8320    cx.assert_editor_state(&format!(
 8321        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8322    ));
 8323    let scroll_position_after_selection =
 8324        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8325    assert_eq!(
 8326        initial_scroll_position, scroll_position_after_selection,
 8327        "Scroll position should not change after selecting all matches"
 8328    );
 8329}
 8330
 8331#[gpui::test]
 8332async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8333    init_test(cx, |_| {});
 8334
 8335    let mut cx = EditorLspTestContext::new_rust(
 8336        lsp::ServerCapabilities {
 8337            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8338            ..Default::default()
 8339        },
 8340        cx,
 8341    )
 8342    .await;
 8343
 8344    cx.set_state(indoc! {"
 8345        line 1
 8346        line 2
 8347        linˇe 3
 8348        line 4
 8349        line 5
 8350    "});
 8351
 8352    // Make an edit
 8353    cx.update_editor(|editor, window, cx| {
 8354        editor.handle_input("X", window, cx);
 8355    });
 8356
 8357    // Move cursor to a different position
 8358    cx.update_editor(|editor, window, cx| {
 8359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8360            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8361        });
 8362    });
 8363
 8364    cx.assert_editor_state(indoc! {"
 8365        line 1
 8366        line 2
 8367        linXe 3
 8368        line 4
 8369        liˇne 5
 8370    "});
 8371
 8372    cx.lsp
 8373        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8374            Ok(Some(vec![lsp::TextEdit::new(
 8375                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8376                "PREFIX ".to_string(),
 8377            )]))
 8378        });
 8379
 8380    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8381        .unwrap()
 8382        .await
 8383        .unwrap();
 8384
 8385    cx.assert_editor_state(indoc! {"
 8386        PREFIX line 1
 8387        line 2
 8388        linXe 3
 8389        line 4
 8390        liˇne 5
 8391    "});
 8392
 8393    // Undo formatting
 8394    cx.update_editor(|editor, window, cx| {
 8395        editor.undo(&Default::default(), window, cx);
 8396    });
 8397
 8398    // Verify cursor moved back to position after edit
 8399    cx.assert_editor_state(indoc! {"
 8400        line 1
 8401        line 2
 8402        linXˇe 3
 8403        line 4
 8404        line 5
 8405    "});
 8406}
 8407
 8408#[gpui::test]
 8409async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8410    init_test(cx, |_| {});
 8411
 8412    let mut cx = EditorTestContext::new(cx).await;
 8413
 8414    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8415    cx.update_editor(|editor, window, cx| {
 8416        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8417    });
 8418
 8419    cx.set_state(indoc! {"
 8420        line 1
 8421        line 2
 8422        linˇe 3
 8423        line 4
 8424        line 5
 8425        line 6
 8426        line 7
 8427        line 8
 8428        line 9
 8429        line 10
 8430    "});
 8431
 8432    let snapshot = cx.buffer_snapshot();
 8433    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8434
 8435    cx.update(|_, cx| {
 8436        provider.update(cx, |provider, _| {
 8437            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8438                id: None,
 8439                edits: vec![(edit_position..edit_position, "X".into())],
 8440                edit_preview: None,
 8441            }))
 8442        })
 8443    });
 8444
 8445    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8446    cx.update_editor(|editor, window, cx| {
 8447        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8448    });
 8449
 8450    cx.assert_editor_state(indoc! {"
 8451        line 1
 8452        line 2
 8453        lineXˇ 3
 8454        line 4
 8455        line 5
 8456        line 6
 8457        line 7
 8458        line 8
 8459        line 9
 8460        line 10
 8461    "});
 8462
 8463    cx.update_editor(|editor, window, cx| {
 8464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8465            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8466        });
 8467    });
 8468
 8469    cx.assert_editor_state(indoc! {"
 8470        line 1
 8471        line 2
 8472        lineX 3
 8473        line 4
 8474        line 5
 8475        line 6
 8476        line 7
 8477        line 8
 8478        line 9
 8479        liˇne 10
 8480    "});
 8481
 8482    cx.update_editor(|editor, window, cx| {
 8483        editor.undo(&Default::default(), window, cx);
 8484    });
 8485
 8486    cx.assert_editor_state(indoc! {"
 8487        line 1
 8488        line 2
 8489        lineˇ 3
 8490        line 4
 8491        line 5
 8492        line 6
 8493        line 7
 8494        line 8
 8495        line 9
 8496        line 10
 8497    "});
 8498}
 8499
 8500#[gpui::test]
 8501async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8502    init_test(cx, |_| {});
 8503
 8504    let mut cx = EditorTestContext::new(cx).await;
 8505    cx.set_state(
 8506        r#"let foo = 2;
 8507lˇet foo = 2;
 8508let fooˇ = 2;
 8509let foo = 2;
 8510let foo = ˇ2;"#,
 8511    );
 8512
 8513    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8514        .unwrap();
 8515    cx.assert_editor_state(
 8516        r#"let foo = 2;
 8517«letˇ» foo = 2;
 8518let «fooˇ» = 2;
 8519let foo = 2;
 8520let foo = «2ˇ»;"#,
 8521    );
 8522
 8523    // noop for multiple selections with different contents
 8524    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8525        .unwrap();
 8526    cx.assert_editor_state(
 8527        r#"let foo = 2;
 8528«letˇ» foo = 2;
 8529let «fooˇ» = 2;
 8530let foo = 2;
 8531let foo = «2ˇ»;"#,
 8532    );
 8533
 8534    // Test last selection direction should be preserved
 8535    cx.set_state(
 8536        r#"let foo = 2;
 8537let foo = 2;
 8538let «fooˇ» = 2;
 8539let «ˇfoo» = 2;
 8540let foo = 2;"#,
 8541    );
 8542
 8543    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8544        .unwrap();
 8545    cx.assert_editor_state(
 8546        r#"let foo = 2;
 8547let foo = 2;
 8548let «fooˇ» = 2;
 8549let «ˇfoo» = 2;
 8550let «ˇfoo» = 2;"#,
 8551    );
 8552}
 8553
 8554#[gpui::test]
 8555async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8556    init_test(cx, |_| {});
 8557
 8558    let mut cx =
 8559        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8560
 8561    cx.assert_editor_state(indoc! {"
 8562        ˇbbb
 8563        ccc
 8564
 8565        bbb
 8566        ccc
 8567        "});
 8568    cx.dispatch_action(SelectPrevious::default());
 8569    cx.assert_editor_state(indoc! {"
 8570                «bbbˇ»
 8571                ccc
 8572
 8573                bbb
 8574                ccc
 8575                "});
 8576    cx.dispatch_action(SelectPrevious::default());
 8577    cx.assert_editor_state(indoc! {"
 8578                «bbbˇ»
 8579                ccc
 8580
 8581                «bbbˇ»
 8582                ccc
 8583                "});
 8584}
 8585
 8586#[gpui::test]
 8587async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8588    init_test(cx, |_| {});
 8589
 8590    let mut cx = EditorTestContext::new(cx).await;
 8591    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 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\nabc");
 8596
 8597    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8598        .unwrap();
 8599    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8600
 8601    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8602    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8603
 8604    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8605    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8606
 8607    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8608        .unwrap();
 8609    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8610
 8611    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8612        .unwrap();
 8613    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8614}
 8615
 8616#[gpui::test]
 8617async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8618    init_test(cx, |_| {});
 8619
 8620    let mut cx = EditorTestContext::new(cx).await;
 8621    cx.set_state("");
 8622
 8623    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8624        .unwrap();
 8625    cx.assert_editor_state("«aˇ»");
 8626    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8627        .unwrap();
 8628    cx.assert_editor_state("«aˇ»");
 8629}
 8630
 8631#[gpui::test]
 8632async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8633    init_test(cx, |_| {});
 8634
 8635    let mut cx = EditorTestContext::new(cx).await;
 8636    cx.set_state(
 8637        r#"let foo = 2;
 8638lˇet foo = 2;
 8639let fooˇ = 2;
 8640let foo = 2;
 8641let foo = ˇ2;"#,
 8642    );
 8643
 8644    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8645        .unwrap();
 8646    cx.assert_editor_state(
 8647        r#"let foo = 2;
 8648«letˇ» foo = 2;
 8649let «fooˇ» = 2;
 8650let foo = 2;
 8651let foo = «2ˇ»;"#,
 8652    );
 8653
 8654    // noop for multiple selections with different contents
 8655    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8656        .unwrap();
 8657    cx.assert_editor_state(
 8658        r#"let foo = 2;
 8659«letˇ» foo = 2;
 8660let «fooˇ» = 2;
 8661let foo = 2;
 8662let foo = «2ˇ»;"#,
 8663    );
 8664}
 8665
 8666#[gpui::test]
 8667async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8668    init_test(cx, |_| {});
 8669
 8670    let mut cx = EditorTestContext::new(cx).await;
 8671    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8672
 8673    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8674        .unwrap();
 8675    // selection direction is preserved
 8676    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8677
 8678    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8679        .unwrap();
 8680    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8681
 8682    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8683    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8684
 8685    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8686    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8687
 8688    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8689        .unwrap();
 8690    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8691
 8692    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8693        .unwrap();
 8694    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8695}
 8696
 8697#[gpui::test]
 8698async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8699    init_test(cx, |_| {});
 8700
 8701    let language = Arc::new(Language::new(
 8702        LanguageConfig::default(),
 8703        Some(tree_sitter_rust::LANGUAGE.into()),
 8704    ));
 8705
 8706    let text = r#"
 8707        use mod1::mod2::{mod3, mod4};
 8708
 8709        fn fn_1(param1: bool, param2: &str) {
 8710            let var1 = "text";
 8711        }
 8712    "#
 8713    .unindent();
 8714
 8715    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8716    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8717    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8718
 8719    editor
 8720        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8721        .await;
 8722
 8723    editor.update_in(cx, |editor, window, cx| {
 8724        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8725            s.select_display_ranges([
 8726                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8727                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8728                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8729            ]);
 8730        });
 8731        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8732    });
 8733    editor.update(cx, |editor, cx| {
 8734        assert_text_with_selections(
 8735            editor,
 8736            indoc! {r#"
 8737                use mod1::mod2::{mod3, «mod4ˇ»};
 8738
 8739                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8740                    let var1 = "«ˇtext»";
 8741                }
 8742            "#},
 8743            cx,
 8744        );
 8745    });
 8746
 8747    editor.update_in(cx, |editor, window, cx| {
 8748        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8749    });
 8750    editor.update(cx, |editor, cx| {
 8751        assert_text_with_selections(
 8752            editor,
 8753            indoc! {r#"
 8754                use mod1::mod2::«{mod3, mod4}ˇ»;
 8755
 8756                «ˇfn fn_1(param1: bool, param2: &str) {
 8757                    let var1 = "text";
 8758 8759            "#},
 8760            cx,
 8761        );
 8762    });
 8763
 8764    editor.update_in(cx, |editor, window, cx| {
 8765        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8766    });
 8767    assert_eq!(
 8768        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8769        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8770    );
 8771
 8772    // Trying to expand the selected syntax node one more time has no effect.
 8773    editor.update_in(cx, |editor, window, cx| {
 8774        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8775    });
 8776    assert_eq!(
 8777        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8778        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8779    );
 8780
 8781    editor.update_in(cx, |editor, window, cx| {
 8782        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8783    });
 8784    editor.update(cx, |editor, cx| {
 8785        assert_text_with_selections(
 8786            editor,
 8787            indoc! {r#"
 8788                use mod1::mod2::«{mod3, mod4}ˇ»;
 8789
 8790                «ˇfn fn_1(param1: bool, param2: &str) {
 8791                    let var1 = "text";
 8792 8793            "#},
 8794            cx,
 8795        );
 8796    });
 8797
 8798    editor.update_in(cx, |editor, window, cx| {
 8799        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8800    });
 8801    editor.update(cx, |editor, cx| {
 8802        assert_text_with_selections(
 8803            editor,
 8804            indoc! {r#"
 8805                use mod1::mod2::{mod3, «mod4ˇ»};
 8806
 8807                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8808                    let var1 = "«ˇtext»";
 8809                }
 8810            "#},
 8811            cx,
 8812        );
 8813    });
 8814
 8815    editor.update_in(cx, |editor, window, cx| {
 8816        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8817    });
 8818    editor.update(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    // Trying to shrink the selected syntax node one more time has no effect.
 8833    editor.update_in(cx, |editor, window, cx| {
 8834        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8835    });
 8836    editor.update_in(cx, |editor, _, cx| {
 8837        assert_text_with_selections(
 8838            editor,
 8839            indoc! {r#"
 8840                use mod1::mod2::{mod3, moˇd4};
 8841
 8842                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8843                    let var1 = "teˇxt";
 8844                }
 8845            "#},
 8846            cx,
 8847        );
 8848    });
 8849
 8850    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8851    // a fold.
 8852    editor.update_in(cx, |editor, window, cx| {
 8853        editor.fold_creases(
 8854            vec![
 8855                Crease::simple(
 8856                    Point::new(0, 21)..Point::new(0, 24),
 8857                    FoldPlaceholder::test(),
 8858                ),
 8859                Crease::simple(
 8860                    Point::new(3, 20)..Point::new(3, 22),
 8861                    FoldPlaceholder::test(),
 8862                ),
 8863            ],
 8864            true,
 8865            window,
 8866            cx,
 8867        );
 8868        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8869    });
 8870    editor.update(cx, |editor, cx| {
 8871        assert_text_with_selections(
 8872            editor,
 8873            indoc! {r#"
 8874                use mod1::mod2::«{mod3, mod4}ˇ»;
 8875
 8876                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8877                    let var1 = "«ˇtext»";
 8878                }
 8879            "#},
 8880            cx,
 8881        );
 8882    });
 8883}
 8884
 8885#[gpui::test]
 8886async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8887    init_test(cx, |_| {});
 8888
 8889    let language = Arc::new(Language::new(
 8890        LanguageConfig::default(),
 8891        Some(tree_sitter_rust::LANGUAGE.into()),
 8892    ));
 8893
 8894    let text = "let a = 2;";
 8895
 8896    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8897    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8898    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8899
 8900    editor
 8901        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8902        .await;
 8903
 8904    // Test case 1: Cursor at end of word
 8905    editor.update_in(cx, |editor, window, cx| {
 8906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8907            s.select_display_ranges([
 8908                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8909            ]);
 8910        });
 8911    });
 8912    editor.update(cx, |editor, cx| {
 8913        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8914    });
 8915    editor.update_in(cx, |editor, window, cx| {
 8916        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 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    // Test case 2: Cursor at end of statement
 8929    editor.update_in(cx, |editor, window, cx| {
 8930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8931            s.select_display_ranges([
 8932                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8933            ]);
 8934        });
 8935    });
 8936    editor.update(cx, |editor, cx| {
 8937        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8938    });
 8939    editor.update_in(cx, |editor, window, cx| {
 8940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8941    });
 8942    editor.update(cx, |editor, cx| {
 8943        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8944    });
 8945}
 8946
 8947#[gpui::test]
 8948async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8949    init_test(cx, |_| {});
 8950
 8951    let language = Arc::new(Language::new(
 8952        LanguageConfig {
 8953            name: "JavaScript".into(),
 8954            ..Default::default()
 8955        },
 8956        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8957    ));
 8958
 8959    let text = r#"
 8960        let a = {
 8961            key: "value",
 8962        };
 8963    "#
 8964    .unindent();
 8965
 8966    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8967    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8968    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8969
 8970    editor
 8971        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8972        .await;
 8973
 8974    // Test case 1: Cursor after '{'
 8975    editor.update_in(cx, |editor, window, cx| {
 8976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8977            s.select_display_ranges([
 8978                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8979            ]);
 8980        });
 8981    });
 8982    editor.update(cx, |editor, cx| {
 8983        assert_text_with_selections(
 8984            editor,
 8985            indoc! {r#"
 8986                let a = {ˇ
 8987                    key: "value",
 8988                };
 8989            "#},
 8990            cx,
 8991        );
 8992    });
 8993    editor.update_in(cx, |editor, window, cx| {
 8994        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8995    });
 8996    editor.update(cx, |editor, cx| {
 8997        assert_text_with_selections(
 8998            editor,
 8999            indoc! {r#"
 9000                let a = «ˇ{
 9001                    key: "value",
 9002                }»;
 9003            "#},
 9004            cx,
 9005        );
 9006    });
 9007
 9008    // Test case 2: Cursor after ':'
 9009    editor.update_in(cx, |editor, window, cx| {
 9010        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9011            s.select_display_ranges([
 9012                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9013            ]);
 9014        });
 9015    });
 9016    editor.update(cx, |editor, cx| {
 9017        assert_text_with_selections(
 9018            editor,
 9019            indoc! {r#"
 9020                let a = {
 9021                    key:ˇ "value",
 9022                };
 9023            "#},
 9024            cx,
 9025        );
 9026    });
 9027    editor.update_in(cx, |editor, window, cx| {
 9028        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9029    });
 9030    editor.update(cx, |editor, cx| {
 9031        assert_text_with_selections(
 9032            editor,
 9033            indoc! {r#"
 9034                let a = {
 9035                    «ˇkey: "value"»,
 9036                };
 9037            "#},
 9038            cx,
 9039        );
 9040    });
 9041    editor.update_in(cx, |editor, window, cx| {
 9042        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9043    });
 9044    editor.update(cx, |editor, cx| {
 9045        assert_text_with_selections(
 9046            editor,
 9047            indoc! {r#"
 9048                let a = «ˇ{
 9049                    key: "value",
 9050                }»;
 9051            "#},
 9052            cx,
 9053        );
 9054    });
 9055
 9056    // Test case 3: Cursor after ','
 9057    editor.update_in(cx, |editor, window, cx| {
 9058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9059            s.select_display_ranges([
 9060                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9061            ]);
 9062        });
 9063    });
 9064    editor.update(cx, |editor, cx| {
 9065        assert_text_with_selections(
 9066            editor,
 9067            indoc! {r#"
 9068                let a = {
 9069                    key: "value",ˇ
 9070                };
 9071            "#},
 9072            cx,
 9073        );
 9074    });
 9075    editor.update_in(cx, |editor, window, cx| {
 9076        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9077    });
 9078    editor.update(cx, |editor, cx| {
 9079        assert_text_with_selections(
 9080            editor,
 9081            indoc! {r#"
 9082                let a = «ˇ{
 9083                    key: "value",
 9084                }»;
 9085            "#},
 9086            cx,
 9087        );
 9088    });
 9089
 9090    // Test case 4: Cursor after ';'
 9091    editor.update_in(cx, |editor, window, cx| {
 9092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9093            s.select_display_ranges([
 9094                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9095            ]);
 9096        });
 9097    });
 9098    editor.update(cx, |editor, cx| {
 9099        assert_text_with_selections(
 9100            editor,
 9101            indoc! {r#"
 9102                let a = {
 9103                    key: "value",
 9104                };ˇ
 9105            "#},
 9106            cx,
 9107        );
 9108    });
 9109    editor.update_in(cx, |editor, window, cx| {
 9110        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9111    });
 9112    editor.update(cx, |editor, cx| {
 9113        assert_text_with_selections(
 9114            editor,
 9115            indoc! {r#"
 9116                «ˇlet a = {
 9117                    key: "value",
 9118                };
 9119                »"#},
 9120            cx,
 9121        );
 9122    });
 9123}
 9124
 9125#[gpui::test]
 9126async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9127    init_test(cx, |_| {});
 9128
 9129    let language = Arc::new(Language::new(
 9130        LanguageConfig::default(),
 9131        Some(tree_sitter_rust::LANGUAGE.into()),
 9132    ));
 9133
 9134    let text = r#"
 9135        use mod1::mod2::{mod3, mod4};
 9136
 9137        fn fn_1(param1: bool, param2: &str) {
 9138            let var1 = "hello world";
 9139        }
 9140    "#
 9141    .unindent();
 9142
 9143    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9144    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9145    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9146
 9147    editor
 9148        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9149        .await;
 9150
 9151    // Test 1: Cursor on a letter of a string word
 9152    editor.update_in(cx, |editor, window, cx| {
 9153        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9154            s.select_display_ranges([
 9155                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9156            ]);
 9157        });
 9158    });
 9159    editor.update_in(cx, |editor, window, cx| {
 9160        assert_text_with_selections(
 9161            editor,
 9162            indoc! {r#"
 9163                use mod1::mod2::{mod3, mod4};
 9164
 9165                fn fn_1(param1: bool, param2: &str) {
 9166                    let var1 = "hˇello world";
 9167                }
 9168            "#},
 9169            cx,
 9170        );
 9171        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9172        assert_text_with_selections(
 9173            editor,
 9174            indoc! {r#"
 9175                use mod1::mod2::{mod3, mod4};
 9176
 9177                fn fn_1(param1: bool, param2: &str) {
 9178                    let var1 = "«ˇhello» world";
 9179                }
 9180            "#},
 9181            cx,
 9182        );
 9183    });
 9184
 9185    // Test 2: Partial selection within a word
 9186    editor.update_in(cx, |editor, window, cx| {
 9187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9188            s.select_display_ranges([
 9189                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9190            ]);
 9191        });
 9192    });
 9193    editor.update_in(cx, |editor, window, cx| {
 9194        assert_text_with_selections(
 9195            editor,
 9196            indoc! {r#"
 9197                use mod1::mod2::{mod3, mod4};
 9198
 9199                fn fn_1(param1: bool, param2: &str) {
 9200                    let var1 = "h«elˇ»lo world";
 9201                }
 9202            "#},
 9203            cx,
 9204        );
 9205        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9206        assert_text_with_selections(
 9207            editor,
 9208            indoc! {r#"
 9209                use mod1::mod2::{mod3, mod4};
 9210
 9211                fn fn_1(param1: bool, param2: &str) {
 9212                    let var1 = "«ˇhello» world";
 9213                }
 9214            "#},
 9215            cx,
 9216        );
 9217    });
 9218
 9219    // Test 3: Complete word already selected
 9220    editor.update_in(cx, |editor, window, cx| {
 9221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9222            s.select_display_ranges([
 9223                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9224            ]);
 9225        });
 9226    });
 9227    editor.update_in(cx, |editor, window, cx| {
 9228        assert_text_with_selections(
 9229            editor,
 9230            indoc! {r#"
 9231                use mod1::mod2::{mod3, mod4};
 9232
 9233                fn fn_1(param1: bool, param2: &str) {
 9234                    let var1 = "«helloˇ» world";
 9235                }
 9236            "#},
 9237            cx,
 9238        );
 9239        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9240        assert_text_with_selections(
 9241            editor,
 9242            indoc! {r#"
 9243                use mod1::mod2::{mod3, mod4};
 9244
 9245                fn fn_1(param1: bool, param2: &str) {
 9246                    let var1 = "«hello worldˇ»";
 9247                }
 9248            "#},
 9249            cx,
 9250        );
 9251    });
 9252
 9253    // Test 4: Selection spanning across words
 9254    editor.update_in(cx, |editor, window, cx| {
 9255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9256            s.select_display_ranges([
 9257                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9258            ]);
 9259        });
 9260    });
 9261    editor.update_in(cx, |editor, window, cx| {
 9262        assert_text_with_selections(
 9263            editor,
 9264            indoc! {r#"
 9265                use mod1::mod2::{mod3, mod4};
 9266
 9267                fn fn_1(param1: bool, param2: &str) {
 9268                    let var1 = "hel«lo woˇ»rld";
 9269                }
 9270            "#},
 9271            cx,
 9272        );
 9273        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9274        assert_text_with_selections(
 9275            editor,
 9276            indoc! {r#"
 9277                use mod1::mod2::{mod3, mod4};
 9278
 9279                fn fn_1(param1: bool, param2: &str) {
 9280                    let var1 = "«ˇhello world»";
 9281                }
 9282            "#},
 9283            cx,
 9284        );
 9285    });
 9286
 9287    // Test 5: Expansion beyond string
 9288    editor.update_in(cx, |editor, window, cx| {
 9289        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9290        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9291        assert_text_with_selections(
 9292            editor,
 9293            indoc! {r#"
 9294                use mod1::mod2::{mod3, mod4};
 9295
 9296                fn fn_1(param1: bool, param2: &str) {
 9297                    «ˇlet var1 = "hello world";»
 9298                }
 9299            "#},
 9300            cx,
 9301        );
 9302    });
 9303}
 9304
 9305#[gpui::test]
 9306async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9307    init_test(cx, |_| {});
 9308
 9309    let mut cx = EditorTestContext::new(cx).await;
 9310
 9311    let language = Arc::new(Language::new(
 9312        LanguageConfig::default(),
 9313        Some(tree_sitter_rust::LANGUAGE.into()),
 9314    ));
 9315
 9316    cx.update_buffer(|buffer, cx| {
 9317        buffer.set_language(Some(language), cx);
 9318    });
 9319
 9320    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9321    cx.update_editor(|editor, window, cx| {
 9322        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9323    });
 9324
 9325    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9326
 9327    cx.set_state(indoc! { r#"fn a() {
 9328          // what
 9329          // a
 9330          // ˇlong
 9331          // method
 9332          // I
 9333          // sure
 9334          // hope
 9335          // it
 9336          // works
 9337    }"# });
 9338
 9339    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9340    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9341    cx.update(|_, cx| {
 9342        multi_buffer.update(cx, |multi_buffer, cx| {
 9343            multi_buffer.set_excerpts_for_path(
 9344                PathKey::for_buffer(&buffer, cx),
 9345                buffer,
 9346                [Point::new(1, 0)..Point::new(1, 0)],
 9347                3,
 9348                cx,
 9349            );
 9350        });
 9351    });
 9352
 9353    let editor2 = cx.new_window_entity(|window, cx| {
 9354        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9355    });
 9356
 9357    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9358    cx.update_editor(|editor, window, cx| {
 9359        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9360            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9361        })
 9362    });
 9363
 9364    cx.assert_editor_state(indoc! { "
 9365        fn a() {
 9366              // what
 9367              // a
 9368        ˇ      // long
 9369              // method"});
 9370
 9371    cx.update_editor(|editor, window, cx| {
 9372        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9373    });
 9374
 9375    // Although we could potentially make the action work when the syntax node
 9376    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9377    // did. Maybe we could also expand the excerpt to contain the range?
 9378    cx.assert_editor_state(indoc! { "
 9379        fn a() {
 9380              // what
 9381              // a
 9382        ˇ      // long
 9383              // method"});
 9384}
 9385
 9386#[gpui::test]
 9387async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9388    init_test(cx, |_| {});
 9389
 9390    let base_text = r#"
 9391        impl A {
 9392            // this is an uncommitted comment
 9393
 9394            fn b() {
 9395                c();
 9396            }
 9397
 9398            // this is another uncommitted comment
 9399
 9400            fn d() {
 9401                // e
 9402                // f
 9403            }
 9404        }
 9405
 9406        fn g() {
 9407            // h
 9408        }
 9409    "#
 9410    .unindent();
 9411
 9412    let text = r#"
 9413        ˇimpl A {
 9414
 9415            fn b() {
 9416                c();
 9417            }
 9418
 9419            fn d() {
 9420                // e
 9421                // f
 9422            }
 9423        }
 9424
 9425        fn g() {
 9426            // h
 9427        }
 9428    "#
 9429    .unindent();
 9430
 9431    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9432    cx.set_state(&text);
 9433    cx.set_head_text(&base_text);
 9434    cx.update_editor(|editor, window, cx| {
 9435        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9436    });
 9437
 9438    cx.assert_state_with_diff(
 9439        "
 9440        ˇimpl A {
 9441      -     // this is an uncommitted comment
 9442
 9443            fn b() {
 9444                c();
 9445            }
 9446
 9447      -     // this is another uncommitted comment
 9448      -
 9449            fn d() {
 9450                // e
 9451                // f
 9452            }
 9453        }
 9454
 9455        fn g() {
 9456            // h
 9457        }
 9458    "
 9459        .unindent(),
 9460    );
 9461
 9462    let expected_display_text = "
 9463        impl A {
 9464            // this is an uncommitted comment
 9465
 9466            fn b() {
 9467 9468            }
 9469
 9470            // this is another uncommitted comment
 9471
 9472            fn d() {
 9473 9474            }
 9475        }
 9476
 9477        fn g() {
 9478 9479        }
 9480        "
 9481    .unindent();
 9482
 9483    cx.update_editor(|editor, window, cx| {
 9484        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9485        assert_eq!(editor.display_text(cx), expected_display_text);
 9486    });
 9487}
 9488
 9489#[gpui::test]
 9490async fn test_autoindent(cx: &mut TestAppContext) {
 9491    init_test(cx, |_| {});
 9492
 9493    let language = Arc::new(
 9494        Language::new(
 9495            LanguageConfig {
 9496                brackets: BracketPairConfig {
 9497                    pairs: vec![
 9498                        BracketPair {
 9499                            start: "{".to_string(),
 9500                            end: "}".to_string(),
 9501                            close: false,
 9502                            surround: false,
 9503                            newline: true,
 9504                        },
 9505                        BracketPair {
 9506                            start: "(".to_string(),
 9507                            end: ")".to_string(),
 9508                            close: false,
 9509                            surround: false,
 9510                            newline: true,
 9511                        },
 9512                    ],
 9513                    ..Default::default()
 9514                },
 9515                ..Default::default()
 9516            },
 9517            Some(tree_sitter_rust::LANGUAGE.into()),
 9518        )
 9519        .with_indents_query(
 9520            r#"
 9521                (_ "(" ")" @end) @indent
 9522                (_ "{" "}" @end) @indent
 9523            "#,
 9524        )
 9525        .unwrap(),
 9526    );
 9527
 9528    let text = "fn a() {}";
 9529
 9530    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9531    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9532    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9533    editor
 9534        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9535        .await;
 9536
 9537    editor.update_in(cx, |editor, window, cx| {
 9538        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9539            s.select_ranges([5..5, 8..8, 9..9])
 9540        });
 9541        editor.newline(&Newline, window, cx);
 9542        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9543        assert_eq!(
 9544            editor.selections.ranges(cx),
 9545            &[
 9546                Point::new(1, 4)..Point::new(1, 4),
 9547                Point::new(3, 4)..Point::new(3, 4),
 9548                Point::new(5, 0)..Point::new(5, 0)
 9549            ]
 9550        );
 9551    });
 9552}
 9553
 9554#[gpui::test]
 9555async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9556    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9557
 9558    let language = Arc::new(
 9559        Language::new(
 9560            LanguageConfig {
 9561                brackets: BracketPairConfig {
 9562                    pairs: vec![
 9563                        BracketPair {
 9564                            start: "{".to_string(),
 9565                            end: "}".to_string(),
 9566                            close: false,
 9567                            surround: false,
 9568                            newline: true,
 9569                        },
 9570                        BracketPair {
 9571                            start: "(".to_string(),
 9572                            end: ")".to_string(),
 9573                            close: false,
 9574                            surround: false,
 9575                            newline: true,
 9576                        },
 9577                    ],
 9578                    ..Default::default()
 9579                },
 9580                ..Default::default()
 9581            },
 9582            Some(tree_sitter_rust::LANGUAGE.into()),
 9583        )
 9584        .with_indents_query(
 9585            r#"
 9586                (_ "(" ")" @end) @indent
 9587                (_ "{" "}" @end) @indent
 9588            "#,
 9589        )
 9590        .unwrap(),
 9591    );
 9592
 9593    let text = "fn a() {}";
 9594
 9595    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9596    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9597    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9598    editor
 9599        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9600        .await;
 9601
 9602    editor.update_in(cx, |editor, window, cx| {
 9603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9604            s.select_ranges([5..5, 8..8, 9..9])
 9605        });
 9606        editor.newline(&Newline, window, cx);
 9607        assert_eq!(
 9608            editor.text(cx),
 9609            indoc!(
 9610                "
 9611                fn a(
 9612
 9613                ) {
 9614
 9615                }
 9616                "
 9617            )
 9618        );
 9619        assert_eq!(
 9620            editor.selections.ranges(cx),
 9621            &[
 9622                Point::new(1, 0)..Point::new(1, 0),
 9623                Point::new(3, 0)..Point::new(3, 0),
 9624                Point::new(5, 0)..Point::new(5, 0)
 9625            ]
 9626        );
 9627    });
 9628}
 9629
 9630#[gpui::test]
 9631async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9632    init_test(cx, |settings| {
 9633        settings.defaults.auto_indent = Some(true);
 9634        settings.languages.0.insert(
 9635            "python".into(),
 9636            LanguageSettingsContent {
 9637                auto_indent: Some(false),
 9638                ..Default::default()
 9639            },
 9640        );
 9641    });
 9642
 9643    let mut cx = EditorTestContext::new(cx).await;
 9644
 9645    let injected_language = Arc::new(
 9646        Language::new(
 9647            LanguageConfig {
 9648                brackets: BracketPairConfig {
 9649                    pairs: vec![
 9650                        BracketPair {
 9651                            start: "{".to_string(),
 9652                            end: "}".to_string(),
 9653                            close: false,
 9654                            surround: false,
 9655                            newline: true,
 9656                        },
 9657                        BracketPair {
 9658                            start: "(".to_string(),
 9659                            end: ")".to_string(),
 9660                            close: true,
 9661                            surround: false,
 9662                            newline: true,
 9663                        },
 9664                    ],
 9665                    ..Default::default()
 9666                },
 9667                name: "python".into(),
 9668                ..Default::default()
 9669            },
 9670            Some(tree_sitter_python::LANGUAGE.into()),
 9671        )
 9672        .with_indents_query(
 9673            r#"
 9674                (_ "(" ")" @end) @indent
 9675                (_ "{" "}" @end) @indent
 9676            "#,
 9677        )
 9678        .unwrap(),
 9679    );
 9680
 9681    let language = Arc::new(
 9682        Language::new(
 9683            LanguageConfig {
 9684                brackets: BracketPairConfig {
 9685                    pairs: vec![
 9686                        BracketPair {
 9687                            start: "{".to_string(),
 9688                            end: "}".to_string(),
 9689                            close: false,
 9690                            surround: false,
 9691                            newline: true,
 9692                        },
 9693                        BracketPair {
 9694                            start: "(".to_string(),
 9695                            end: ")".to_string(),
 9696                            close: true,
 9697                            surround: false,
 9698                            newline: true,
 9699                        },
 9700                    ],
 9701                    ..Default::default()
 9702                },
 9703                name: LanguageName::new("rust"),
 9704                ..Default::default()
 9705            },
 9706            Some(tree_sitter_rust::LANGUAGE.into()),
 9707        )
 9708        .with_indents_query(
 9709            r#"
 9710                (_ "(" ")" @end) @indent
 9711                (_ "{" "}" @end) @indent
 9712            "#,
 9713        )
 9714        .unwrap()
 9715        .with_injection_query(
 9716            r#"
 9717            (macro_invocation
 9718                macro: (identifier) @_macro_name
 9719                (token_tree) @injection.content
 9720                (#set! injection.language "python"))
 9721           "#,
 9722        )
 9723        .unwrap(),
 9724    );
 9725
 9726    cx.language_registry().add(injected_language);
 9727    cx.language_registry().add(language.clone());
 9728
 9729    cx.update_buffer(|buffer, cx| {
 9730        buffer.set_language(Some(language), cx);
 9731    });
 9732
 9733    cx.set_state(r#"struct A {ˇ}"#);
 9734
 9735    cx.update_editor(|editor, window, cx| {
 9736        editor.newline(&Default::default(), window, cx);
 9737    });
 9738
 9739    cx.assert_editor_state(indoc!(
 9740        "struct A {
 9741            ˇ
 9742        }"
 9743    ));
 9744
 9745    cx.set_state(r#"select_biased!(ˇ)"#);
 9746
 9747    cx.update_editor(|editor, window, cx| {
 9748        editor.newline(&Default::default(), window, cx);
 9749        editor.handle_input("def ", window, cx);
 9750        editor.handle_input("(", window, cx);
 9751        editor.newline(&Default::default(), window, cx);
 9752        editor.handle_input("a", window, cx);
 9753    });
 9754
 9755    cx.assert_editor_state(indoc!(
 9756        "select_biased!(
 9757        def (
 9758 9759        )
 9760        )"
 9761    ));
 9762}
 9763
 9764#[gpui::test]
 9765async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9766    init_test(cx, |_| {});
 9767
 9768    {
 9769        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9770        cx.set_state(indoc! {"
 9771            impl A {
 9772
 9773                fn b() {}
 9774
 9775            «fn c() {
 9776
 9777            }ˇ»
 9778            }
 9779        "});
 9780
 9781        cx.update_editor(|editor, window, cx| {
 9782            editor.autoindent(&Default::default(), window, cx);
 9783        });
 9784
 9785        cx.assert_editor_state(indoc! {"
 9786            impl A {
 9787
 9788                fn b() {}
 9789
 9790                «fn c() {
 9791
 9792                }ˇ»
 9793            }
 9794        "});
 9795    }
 9796
 9797    {
 9798        let mut cx = EditorTestContext::new_multibuffer(
 9799            cx,
 9800            [indoc! { "
 9801                impl A {
 9802                «
 9803                // a
 9804                fn b(){}
 9805                »
 9806                «
 9807                    }
 9808                    fn c(){}
 9809                »
 9810            "}],
 9811        );
 9812
 9813        let buffer = cx.update_editor(|editor, _, cx| {
 9814            let buffer = editor.buffer().update(cx, |buffer, _| {
 9815                buffer.all_buffers().iter().next().unwrap().clone()
 9816            });
 9817            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9818            buffer
 9819        });
 9820
 9821        cx.run_until_parked();
 9822        cx.update_editor(|editor, window, cx| {
 9823            editor.select_all(&Default::default(), window, cx);
 9824            editor.autoindent(&Default::default(), window, cx)
 9825        });
 9826        cx.run_until_parked();
 9827
 9828        cx.update(|_, cx| {
 9829            assert_eq!(
 9830                buffer.read(cx).text(),
 9831                indoc! { "
 9832                    impl A {
 9833
 9834                        // a
 9835                        fn b(){}
 9836
 9837
 9838                    }
 9839                    fn c(){}
 9840
 9841                " }
 9842            )
 9843        });
 9844    }
 9845}
 9846
 9847#[gpui::test]
 9848async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9849    init_test(cx, |_| {});
 9850
 9851    let mut cx = EditorTestContext::new(cx).await;
 9852
 9853    let language = Arc::new(Language::new(
 9854        LanguageConfig {
 9855            brackets: BracketPairConfig {
 9856                pairs: vec![
 9857                    BracketPair {
 9858                        start: "{".to_string(),
 9859                        end: "}".to_string(),
 9860                        close: true,
 9861                        surround: true,
 9862                        newline: true,
 9863                    },
 9864                    BracketPair {
 9865                        start: "(".to_string(),
 9866                        end: ")".to_string(),
 9867                        close: true,
 9868                        surround: true,
 9869                        newline: true,
 9870                    },
 9871                    BracketPair {
 9872                        start: "/*".to_string(),
 9873                        end: " */".to_string(),
 9874                        close: true,
 9875                        surround: true,
 9876                        newline: true,
 9877                    },
 9878                    BracketPair {
 9879                        start: "[".to_string(),
 9880                        end: "]".to_string(),
 9881                        close: false,
 9882                        surround: false,
 9883                        newline: true,
 9884                    },
 9885                    BracketPair {
 9886                        start: "\"".to_string(),
 9887                        end: "\"".to_string(),
 9888                        close: true,
 9889                        surround: true,
 9890                        newline: false,
 9891                    },
 9892                    BracketPair {
 9893                        start: "<".to_string(),
 9894                        end: ">".to_string(),
 9895                        close: false,
 9896                        surround: true,
 9897                        newline: true,
 9898                    },
 9899                ],
 9900                ..Default::default()
 9901            },
 9902            autoclose_before: "})]".to_string(),
 9903            ..Default::default()
 9904        },
 9905        Some(tree_sitter_rust::LANGUAGE.into()),
 9906    ));
 9907
 9908    cx.language_registry().add(language.clone());
 9909    cx.update_buffer(|buffer, cx| {
 9910        buffer.set_language(Some(language), cx);
 9911    });
 9912
 9913    cx.set_state(
 9914        &r#"
 9915            🏀ˇ
 9916            εˇ
 9917            ❤️ˇ
 9918        "#
 9919        .unindent(),
 9920    );
 9921
 9922    // autoclose multiple nested brackets at multiple cursors
 9923    cx.update_editor(|editor, window, cx| {
 9924        editor.handle_input("{", window, cx);
 9925        editor.handle_input("{", window, cx);
 9926        editor.handle_input("{", window, cx);
 9927    });
 9928    cx.assert_editor_state(
 9929        &"
 9930            🏀{{{ˇ}}}
 9931            ε{{{ˇ}}}
 9932            ❤️{{{ˇ}}}
 9933        "
 9934        .unindent(),
 9935    );
 9936
 9937    // insert a different closing bracket
 9938    cx.update_editor(|editor, window, cx| {
 9939        editor.handle_input(")", window, cx);
 9940    });
 9941    cx.assert_editor_state(
 9942        &"
 9943            🏀{{{)ˇ}}}
 9944            ε{{{)ˇ}}}
 9945            ❤️{{{)ˇ}}}
 9946        "
 9947        .unindent(),
 9948    );
 9949
 9950    // skip over the auto-closed brackets when typing a closing bracket
 9951    cx.update_editor(|editor, window, cx| {
 9952        editor.move_right(&MoveRight, window, cx);
 9953        editor.handle_input("}", window, cx);
 9954        editor.handle_input("}", window, cx);
 9955        editor.handle_input("}", window, cx);
 9956    });
 9957    cx.assert_editor_state(
 9958        &"
 9959            🏀{{{)}}}}ˇ
 9960            ε{{{)}}}}ˇ
 9961            ❤️{{{)}}}}ˇ
 9962        "
 9963        .unindent(),
 9964    );
 9965
 9966    // autoclose multi-character pairs
 9967    cx.set_state(
 9968        &"
 9969            ˇ
 9970            ˇ
 9971        "
 9972        .unindent(),
 9973    );
 9974    cx.update_editor(|editor, window, cx| {
 9975        editor.handle_input("/", window, cx);
 9976        editor.handle_input("*", window, cx);
 9977    });
 9978    cx.assert_editor_state(
 9979        &"
 9980            /*ˇ */
 9981            /*ˇ */
 9982        "
 9983        .unindent(),
 9984    );
 9985
 9986    // one cursor autocloses a multi-character pair, one cursor
 9987    // does not autoclose.
 9988    cx.set_state(
 9989        &"
 9990 9991            ˇ
 9992        "
 9993        .unindent(),
 9994    );
 9995    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9996    cx.assert_editor_state(
 9997        &"
 9998            /*ˇ */
 999910000        "
10001        .unindent(),
10002    );
10003
10004    // Don't autoclose if the next character isn't whitespace and isn't
10005    // listed in the language's "autoclose_before" section.
10006    cx.set_state("ˇa b");
10007    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10008    cx.assert_editor_state("{ˇa b");
10009
10010    // Don't autoclose if `close` is false for the bracket pair
10011    cx.set_state("ˇ");
10012    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10013    cx.assert_editor_state("");
10014
10015    // Surround with brackets if text is selected
10016    cx.set_state("«aˇ» b");
10017    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10018    cx.assert_editor_state("{«aˇ»} b");
10019
10020    // Autoclose when not immediately after a word character
10021    cx.set_state("a ˇ");
10022    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10023    cx.assert_editor_state("a \"ˇ\"");
10024
10025    // Autoclose pair where the start and end characters are the same
10026    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10027    cx.assert_editor_state("a \"\"ˇ");
10028
10029    // Don't autoclose when immediately after a word character
10030    cx.set_state("");
10031    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10032    cx.assert_editor_state("a\"ˇ");
10033
10034    // Do autoclose when after a non-word character
10035    cx.set_state("");
10036    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10037    cx.assert_editor_state("{\"ˇ\"");
10038
10039    // Non identical pairs autoclose regardless of preceding character
10040    cx.set_state("");
10041    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10042    cx.assert_editor_state("a{ˇ}");
10043
10044    // Don't autoclose pair if autoclose is disabled
10045    cx.set_state("ˇ");
10046    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10047    cx.assert_editor_state("");
10048
10049    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10050    cx.set_state("«aˇ» b");
10051    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10052    cx.assert_editor_state("<«aˇ»> b");
10053}
10054
10055#[gpui::test]
10056async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10057    init_test(cx, |settings| {
10058        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10059    });
10060
10061    let mut cx = EditorTestContext::new(cx).await;
10062
10063    let language = Arc::new(Language::new(
10064        LanguageConfig {
10065            brackets: BracketPairConfig {
10066                pairs: vec![
10067                    BracketPair {
10068                        start: "{".to_string(),
10069                        end: "}".to_string(),
10070                        close: true,
10071                        surround: true,
10072                        newline: true,
10073                    },
10074                    BracketPair {
10075                        start: "(".to_string(),
10076                        end: ")".to_string(),
10077                        close: true,
10078                        surround: true,
10079                        newline: true,
10080                    },
10081                    BracketPair {
10082                        start: "[".to_string(),
10083                        end: "]".to_string(),
10084                        close: false,
10085                        surround: false,
10086                        newline: true,
10087                    },
10088                ],
10089                ..Default::default()
10090            },
10091            autoclose_before: "})]".to_string(),
10092            ..Default::default()
10093        },
10094        Some(tree_sitter_rust::LANGUAGE.into()),
10095    ));
10096
10097    cx.language_registry().add(language.clone());
10098    cx.update_buffer(|buffer, cx| {
10099        buffer.set_language(Some(language), cx);
10100    });
10101
10102    cx.set_state(
10103        &"
10104            ˇ
10105            ˇ
10106            ˇ
10107        "
10108        .unindent(),
10109    );
10110
10111    // ensure only matching closing brackets are skipped over
10112    cx.update_editor(|editor, window, cx| {
10113        editor.handle_input("}", window, cx);
10114        editor.move_left(&MoveLeft, window, cx);
10115        editor.handle_input(")", window, cx);
10116        editor.move_left(&MoveLeft, window, cx);
10117    });
10118    cx.assert_editor_state(
10119        &"
10120            ˇ)}
10121            ˇ)}
10122            ˇ)}
10123        "
10124        .unindent(),
10125    );
10126
10127    // skip-over closing brackets at multiple cursors
10128    cx.update_editor(|editor, window, cx| {
10129        editor.handle_input(")", window, cx);
10130        editor.handle_input("}", window, cx);
10131    });
10132    cx.assert_editor_state(
10133        &"
10134            )}ˇ
10135            )}ˇ
10136            )}ˇ
10137        "
10138        .unindent(),
10139    );
10140
10141    // ignore non-close brackets
10142    cx.update_editor(|editor, window, cx| {
10143        editor.handle_input("]", window, cx);
10144        editor.move_left(&MoveLeft, window, cx);
10145        editor.handle_input("]", window, cx);
10146    });
10147    cx.assert_editor_state(
10148        &"
10149            )}]ˇ]
10150            )}]ˇ]
10151            )}]ˇ]
10152        "
10153        .unindent(),
10154    );
10155}
10156
10157#[gpui::test]
10158async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10159    init_test(cx, |_| {});
10160
10161    let mut cx = EditorTestContext::new(cx).await;
10162
10163    let html_language = Arc::new(
10164        Language::new(
10165            LanguageConfig {
10166                name: "HTML".into(),
10167                brackets: BracketPairConfig {
10168                    pairs: vec![
10169                        BracketPair {
10170                            start: "<".into(),
10171                            end: ">".into(),
10172                            close: true,
10173                            ..Default::default()
10174                        },
10175                        BracketPair {
10176                            start: "{".into(),
10177                            end: "}".into(),
10178                            close: true,
10179                            ..Default::default()
10180                        },
10181                        BracketPair {
10182                            start: "(".into(),
10183                            end: ")".into(),
10184                            close: true,
10185                            ..Default::default()
10186                        },
10187                    ],
10188                    ..Default::default()
10189                },
10190                autoclose_before: "})]>".into(),
10191                ..Default::default()
10192            },
10193            Some(tree_sitter_html::LANGUAGE.into()),
10194        )
10195        .with_injection_query(
10196            r#"
10197            (script_element
10198                (raw_text) @injection.content
10199                (#set! injection.language "javascript"))
10200            "#,
10201        )
10202        .unwrap(),
10203    );
10204
10205    let javascript_language = Arc::new(Language::new(
10206        LanguageConfig {
10207            name: "JavaScript".into(),
10208            brackets: BracketPairConfig {
10209                pairs: vec![
10210                    BracketPair {
10211                        start: "/*".into(),
10212                        end: " */".into(),
10213                        close: true,
10214                        ..Default::default()
10215                    },
10216                    BracketPair {
10217                        start: "{".into(),
10218                        end: "}".into(),
10219                        close: true,
10220                        ..Default::default()
10221                    },
10222                    BracketPair {
10223                        start: "(".into(),
10224                        end: ")".into(),
10225                        close: true,
10226                        ..Default::default()
10227                    },
10228                ],
10229                ..Default::default()
10230            },
10231            autoclose_before: "})]>".into(),
10232            ..Default::default()
10233        },
10234        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10235    ));
10236
10237    cx.language_registry().add(html_language.clone());
10238    cx.language_registry().add(javascript_language);
10239    cx.executor().run_until_parked();
10240
10241    cx.update_buffer(|buffer, cx| {
10242        buffer.set_language(Some(html_language), cx);
10243    });
10244
10245    cx.set_state(
10246        &r#"
10247            <body>ˇ
10248                <script>
10249                    var x = 1;ˇ
10250                </script>
10251            </body>ˇ
10252        "#
10253        .unindent(),
10254    );
10255
10256    // Precondition: different languages are active at different locations.
10257    cx.update_editor(|editor, window, cx| {
10258        let snapshot = editor.snapshot(window, cx);
10259        let cursors = editor.selections.ranges::<usize>(cx);
10260        let languages = cursors
10261            .iter()
10262            .map(|c| snapshot.language_at(c.start).unwrap().name())
10263            .collect::<Vec<_>>();
10264        assert_eq!(
10265            languages,
10266            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10267        );
10268    });
10269
10270    // Angle brackets autoclose in HTML, but not JavaScript.
10271    cx.update_editor(|editor, window, cx| {
10272        editor.handle_input("<", window, cx);
10273        editor.handle_input("a", window, cx);
10274    });
10275    cx.assert_editor_state(
10276        &r#"
10277            <body><aˇ>
10278                <script>
10279                    var x = 1;<aˇ
10280                </script>
10281            </body><aˇ>
10282        "#
10283        .unindent(),
10284    );
10285
10286    // Curly braces and parens autoclose in both HTML and JavaScript.
10287    cx.update_editor(|editor, window, cx| {
10288        editor.handle_input(" b=", window, cx);
10289        editor.handle_input("{", window, cx);
10290        editor.handle_input("c", window, cx);
10291        editor.handle_input("(", window, cx);
10292    });
10293    cx.assert_editor_state(
10294        &r#"
10295            <body><a b={c(ˇ)}>
10296                <script>
10297                    var x = 1;<a b={c(ˇ)}
10298                </script>
10299            </body><a b={c(ˇ)}>
10300        "#
10301        .unindent(),
10302    );
10303
10304    // Brackets that were already autoclosed are skipped.
10305    cx.update_editor(|editor, window, cx| {
10306        editor.handle_input(")", window, cx);
10307        editor.handle_input("d", window, cx);
10308        editor.handle_input("}", window, cx);
10309    });
10310    cx.assert_editor_state(
10311        &r#"
10312            <body><a b={c()d}ˇ>
10313                <script>
10314                    var x = 1;<a b={c()d}ˇ
10315                </script>
10316            </body><a b={c()d}ˇ>
10317        "#
10318        .unindent(),
10319    );
10320    cx.update_editor(|editor, window, cx| {
10321        editor.handle_input(">", window, cx);
10322    });
10323    cx.assert_editor_state(
10324        &r#"
10325            <body><a b={c()d}>ˇ
10326                <script>
10327                    var x = 1;<a b={c()d}>ˇ
10328                </script>
10329            </body><a b={c()d}>ˇ
10330        "#
10331        .unindent(),
10332    );
10333
10334    // Reset
10335    cx.set_state(
10336        &r#"
10337            <body>ˇ
10338                <script>
10339                    var x = 1;ˇ
10340                </script>
10341            </body>ˇ
10342        "#
10343        .unindent(),
10344    );
10345
10346    cx.update_editor(|editor, window, cx| {
10347        editor.handle_input("<", window, cx);
10348    });
10349    cx.assert_editor_state(
10350        &r#"
10351            <body><ˇ>
10352                <script>
10353                    var x = 1;<ˇ
10354                </script>
10355            </body><ˇ>
10356        "#
10357        .unindent(),
10358    );
10359
10360    // When backspacing, the closing angle brackets are removed.
10361    cx.update_editor(|editor, window, cx| {
10362        editor.backspace(&Backspace, window, cx);
10363    });
10364    cx.assert_editor_state(
10365        &r#"
10366            <body>ˇ
10367                <script>
10368                    var x = 1;ˇ
10369                </script>
10370            </body>ˇ
10371        "#
10372        .unindent(),
10373    );
10374
10375    // Block comments autoclose in JavaScript, but not HTML.
10376    cx.update_editor(|editor, window, cx| {
10377        editor.handle_input("/", window, cx);
10378        editor.handle_input("*", window, cx);
10379    });
10380    cx.assert_editor_state(
10381        &r#"
10382            <body>/*ˇ
10383                <script>
10384                    var x = 1;/*ˇ */
10385                </script>
10386            </body>/*ˇ
10387        "#
10388        .unindent(),
10389    );
10390}
10391
10392#[gpui::test]
10393async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10394    init_test(cx, |_| {});
10395
10396    let mut cx = EditorTestContext::new(cx).await;
10397
10398    let rust_language = Arc::new(
10399        Language::new(
10400            LanguageConfig {
10401                name: "Rust".into(),
10402                brackets: serde_json::from_value(json!([
10403                    { "start": "{", "end": "}", "close": true, "newline": true },
10404                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10405                ]))
10406                .unwrap(),
10407                autoclose_before: "})]>".into(),
10408                ..Default::default()
10409            },
10410            Some(tree_sitter_rust::LANGUAGE.into()),
10411        )
10412        .with_override_query("(string_literal) @string")
10413        .unwrap(),
10414    );
10415
10416    cx.language_registry().add(rust_language.clone());
10417    cx.update_buffer(|buffer, cx| {
10418        buffer.set_language(Some(rust_language), cx);
10419    });
10420
10421    cx.set_state(
10422        &r#"
10423            let x = ˇ
10424        "#
10425        .unindent(),
10426    );
10427
10428    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10429    cx.update_editor(|editor, window, cx| {
10430        editor.handle_input("\"", window, cx);
10431    });
10432    cx.assert_editor_state(
10433        &r#"
10434            let x = "ˇ"
10435        "#
10436        .unindent(),
10437    );
10438
10439    // Inserting another quotation mark. The cursor moves across the existing
10440    // automatically-inserted quotation mark.
10441    cx.update_editor(|editor, window, cx| {
10442        editor.handle_input("\"", window, cx);
10443    });
10444    cx.assert_editor_state(
10445        &r#"
10446            let x = ""ˇ
10447        "#
10448        .unindent(),
10449    );
10450
10451    // Reset
10452    cx.set_state(
10453        &r#"
10454            let x = ˇ
10455        "#
10456        .unindent(),
10457    );
10458
10459    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10460    cx.update_editor(|editor, window, cx| {
10461        editor.handle_input("\"", window, cx);
10462        editor.handle_input(" ", window, cx);
10463        editor.move_left(&Default::default(), window, cx);
10464        editor.handle_input("\\", window, cx);
10465        editor.handle_input("\"", window, cx);
10466    });
10467    cx.assert_editor_state(
10468        &r#"
10469            let x = "\"ˇ "
10470        "#
10471        .unindent(),
10472    );
10473
10474    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10475    // mark. Nothing is inserted.
10476    cx.update_editor(|editor, window, cx| {
10477        editor.move_right(&Default::default(), window, cx);
10478        editor.handle_input("\"", window, cx);
10479    });
10480    cx.assert_editor_state(
10481        &r#"
10482            let x = "\" "ˇ
10483        "#
10484        .unindent(),
10485    );
10486}
10487
10488#[gpui::test]
10489async fn test_surround_with_pair(cx: &mut TestAppContext) {
10490    init_test(cx, |_| {});
10491
10492    let language = Arc::new(Language::new(
10493        LanguageConfig {
10494            brackets: BracketPairConfig {
10495                pairs: vec![
10496                    BracketPair {
10497                        start: "{".to_string(),
10498                        end: "}".to_string(),
10499                        close: true,
10500                        surround: true,
10501                        newline: true,
10502                    },
10503                    BracketPair {
10504                        start: "/* ".to_string(),
10505                        end: "*/".to_string(),
10506                        close: true,
10507                        surround: true,
10508                        ..Default::default()
10509                    },
10510                ],
10511                ..Default::default()
10512            },
10513            ..Default::default()
10514        },
10515        Some(tree_sitter_rust::LANGUAGE.into()),
10516    ));
10517
10518    let text = r#"
10519        a
10520        b
10521        c
10522    "#
10523    .unindent();
10524
10525    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10526    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10527    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10528    editor
10529        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10530        .await;
10531
10532    editor.update_in(cx, |editor, window, cx| {
10533        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10534            s.select_display_ranges([
10535                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10536                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10537                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10538            ])
10539        });
10540
10541        editor.handle_input("{", window, cx);
10542        editor.handle_input("{", window, cx);
10543        editor.handle_input("{", window, cx);
10544        assert_eq!(
10545            editor.text(cx),
10546            "
10547                {{{a}}}
10548                {{{b}}}
10549                {{{c}}}
10550            "
10551            .unindent()
10552        );
10553        assert_eq!(
10554            editor.selections.display_ranges(cx),
10555            [
10556                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10557                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10558                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10559            ]
10560        );
10561
10562        editor.undo(&Undo, window, cx);
10563        editor.undo(&Undo, window, cx);
10564        editor.undo(&Undo, window, cx);
10565        assert_eq!(
10566            editor.text(cx),
10567            "
10568                a
10569                b
10570                c
10571            "
10572            .unindent()
10573        );
10574        assert_eq!(
10575            editor.selections.display_ranges(cx),
10576            [
10577                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10578                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10579                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10580            ]
10581        );
10582
10583        // Ensure inserting the first character of a multi-byte bracket pair
10584        // doesn't surround the selections with the bracket.
10585        editor.handle_input("/", window, cx);
10586        assert_eq!(
10587            editor.text(cx),
10588            "
10589                /
10590                /
10591                /
10592            "
10593            .unindent()
10594        );
10595        assert_eq!(
10596            editor.selections.display_ranges(cx),
10597            [
10598                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10599                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10600                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10601            ]
10602        );
10603
10604        editor.undo(&Undo, window, cx);
10605        assert_eq!(
10606            editor.text(cx),
10607            "
10608                a
10609                b
10610                c
10611            "
10612            .unindent()
10613        );
10614        assert_eq!(
10615            editor.selections.display_ranges(cx),
10616            [
10617                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10618                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10619                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10620            ]
10621        );
10622
10623        // Ensure inserting the last character of a multi-byte bracket pair
10624        // doesn't surround the selections with the bracket.
10625        editor.handle_input("*", window, cx);
10626        assert_eq!(
10627            editor.text(cx),
10628            "
10629                *
10630                *
10631                *
10632            "
10633            .unindent()
10634        );
10635        assert_eq!(
10636            editor.selections.display_ranges(cx),
10637            [
10638                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10639                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10640                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10641            ]
10642        );
10643    });
10644}
10645
10646#[gpui::test]
10647async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10648    init_test(cx, |_| {});
10649
10650    let language = Arc::new(Language::new(
10651        LanguageConfig {
10652            brackets: BracketPairConfig {
10653                pairs: vec![BracketPair {
10654                    start: "{".to_string(),
10655                    end: "}".to_string(),
10656                    close: true,
10657                    surround: true,
10658                    newline: true,
10659                }],
10660                ..Default::default()
10661            },
10662            autoclose_before: "}".to_string(),
10663            ..Default::default()
10664        },
10665        Some(tree_sitter_rust::LANGUAGE.into()),
10666    ));
10667
10668    let text = r#"
10669        a
10670        b
10671        c
10672    "#
10673    .unindent();
10674
10675    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10676    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10677    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10678    editor
10679        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10680        .await;
10681
10682    editor.update_in(cx, |editor, window, cx| {
10683        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10684            s.select_ranges([
10685                Point::new(0, 1)..Point::new(0, 1),
10686                Point::new(1, 1)..Point::new(1, 1),
10687                Point::new(2, 1)..Point::new(2, 1),
10688            ])
10689        });
10690
10691        editor.handle_input("{", window, cx);
10692        editor.handle_input("{", window, cx);
10693        editor.handle_input("_", window, cx);
10694        assert_eq!(
10695            editor.text(cx),
10696            "
10697                a{{_}}
10698                b{{_}}
10699                c{{_}}
10700            "
10701            .unindent()
10702        );
10703        assert_eq!(
10704            editor.selections.ranges::<Point>(cx),
10705            [
10706                Point::new(0, 4)..Point::new(0, 4),
10707                Point::new(1, 4)..Point::new(1, 4),
10708                Point::new(2, 4)..Point::new(2, 4)
10709            ]
10710        );
10711
10712        editor.backspace(&Default::default(), window, cx);
10713        editor.backspace(&Default::default(), window, cx);
10714        assert_eq!(
10715            editor.text(cx),
10716            "
10717                a{}
10718                b{}
10719                c{}
10720            "
10721            .unindent()
10722        );
10723        assert_eq!(
10724            editor.selections.ranges::<Point>(cx),
10725            [
10726                Point::new(0, 2)..Point::new(0, 2),
10727                Point::new(1, 2)..Point::new(1, 2),
10728                Point::new(2, 2)..Point::new(2, 2)
10729            ]
10730        );
10731
10732        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10733        assert_eq!(
10734            editor.text(cx),
10735            "
10736                a
10737                b
10738                c
10739            "
10740            .unindent()
10741        );
10742        assert_eq!(
10743            editor.selections.ranges::<Point>(cx),
10744            [
10745                Point::new(0, 1)..Point::new(0, 1),
10746                Point::new(1, 1)..Point::new(1, 1),
10747                Point::new(2, 1)..Point::new(2, 1)
10748            ]
10749        );
10750    });
10751}
10752
10753#[gpui::test]
10754async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10755    init_test(cx, |settings| {
10756        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10757    });
10758
10759    let mut cx = EditorTestContext::new(cx).await;
10760
10761    let language = Arc::new(Language::new(
10762        LanguageConfig {
10763            brackets: BracketPairConfig {
10764                pairs: vec![
10765                    BracketPair {
10766                        start: "{".to_string(),
10767                        end: "}".to_string(),
10768                        close: true,
10769                        surround: true,
10770                        newline: true,
10771                    },
10772                    BracketPair {
10773                        start: "(".to_string(),
10774                        end: ")".to_string(),
10775                        close: true,
10776                        surround: true,
10777                        newline: true,
10778                    },
10779                    BracketPair {
10780                        start: "[".to_string(),
10781                        end: "]".to_string(),
10782                        close: false,
10783                        surround: true,
10784                        newline: true,
10785                    },
10786                ],
10787                ..Default::default()
10788            },
10789            autoclose_before: "})]".to_string(),
10790            ..Default::default()
10791        },
10792        Some(tree_sitter_rust::LANGUAGE.into()),
10793    ));
10794
10795    cx.language_registry().add(language.clone());
10796    cx.update_buffer(|buffer, cx| {
10797        buffer.set_language(Some(language), cx);
10798    });
10799
10800    cx.set_state(
10801        &"
10802            {(ˇ)}
10803            [[ˇ]]
10804            {(ˇ)}
10805        "
10806        .unindent(),
10807    );
10808
10809    cx.update_editor(|editor, window, cx| {
10810        editor.backspace(&Default::default(), window, cx);
10811        editor.backspace(&Default::default(), window, cx);
10812    });
10813
10814    cx.assert_editor_state(
10815        &"
10816            ˇ
10817            ˇ]]
10818            ˇ
10819        "
10820        .unindent(),
10821    );
10822
10823    cx.update_editor(|editor, window, cx| {
10824        editor.handle_input("{", window, cx);
10825        editor.handle_input("{", window, cx);
10826        editor.move_right(&MoveRight, window, cx);
10827        editor.move_right(&MoveRight, window, cx);
10828        editor.move_left(&MoveLeft, window, cx);
10829        editor.move_left(&MoveLeft, window, cx);
10830        editor.backspace(&Default::default(), window, cx);
10831    });
10832
10833    cx.assert_editor_state(
10834        &"
10835            {ˇ}
10836            {ˇ}]]
10837            {ˇ}
10838        "
10839        .unindent(),
10840    );
10841
10842    cx.update_editor(|editor, window, cx| {
10843        editor.backspace(&Default::default(), window, cx);
10844    });
10845
10846    cx.assert_editor_state(
10847        &"
10848            ˇ
10849            ˇ]]
10850            ˇ
10851        "
10852        .unindent(),
10853    );
10854}
10855
10856#[gpui::test]
10857async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10858    init_test(cx, |_| {});
10859
10860    let language = Arc::new(Language::new(
10861        LanguageConfig::default(),
10862        Some(tree_sitter_rust::LANGUAGE.into()),
10863    ));
10864
10865    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10866    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10867    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10868    editor
10869        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10870        .await;
10871
10872    editor.update_in(cx, |editor, window, cx| {
10873        editor.set_auto_replace_emoji_shortcode(true);
10874
10875        editor.handle_input("Hello ", window, cx);
10876        editor.handle_input(":wave", window, cx);
10877        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10878
10879        editor.handle_input(":", window, cx);
10880        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10881
10882        editor.handle_input(" :smile", window, cx);
10883        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10884
10885        editor.handle_input(":", window, cx);
10886        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10887
10888        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10889        editor.handle_input(":wave", window, cx);
10890        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10891
10892        editor.handle_input(":", window, cx);
10893        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10894
10895        editor.handle_input(":1", window, cx);
10896        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10897
10898        editor.handle_input(":", window, cx);
10899        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10900
10901        // Ensure shortcode does not get replaced when it is part of a word
10902        editor.handle_input(" Test:wave", window, cx);
10903        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10904
10905        editor.handle_input(":", window, cx);
10906        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10907
10908        editor.set_auto_replace_emoji_shortcode(false);
10909
10910        // Ensure shortcode does not get replaced when auto replace is off
10911        editor.handle_input(" :wave", window, cx);
10912        assert_eq!(
10913            editor.text(cx),
10914            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10915        );
10916
10917        editor.handle_input(":", window, cx);
10918        assert_eq!(
10919            editor.text(cx),
10920            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10921        );
10922    });
10923}
10924
10925#[gpui::test]
10926async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10927    init_test(cx, |_| {});
10928
10929    let (text, insertion_ranges) = marked_text_ranges(
10930        indoc! {"
10931            ˇ
10932        "},
10933        false,
10934    );
10935
10936    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10937    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10938
10939    _ = editor.update_in(cx, |editor, window, cx| {
10940        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10941
10942        editor
10943            .insert_snippet(&insertion_ranges, snippet, window, cx)
10944            .unwrap();
10945
10946        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10947            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10948            assert_eq!(editor.text(cx), expected_text);
10949            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10950        }
10951
10952        assert(
10953            editor,
10954            cx,
10955            indoc! {"
10956            type «» =•
10957            "},
10958        );
10959
10960        assert!(editor.context_menu_visible(), "There should be a matches");
10961    });
10962}
10963
10964#[gpui::test]
10965async fn test_snippets(cx: &mut TestAppContext) {
10966    init_test(cx, |_| {});
10967
10968    let mut cx = EditorTestContext::new(cx).await;
10969
10970    cx.set_state(indoc! {"
10971        a.ˇ b
10972        a.ˇ b
10973        a.ˇ b
10974    "});
10975
10976    cx.update_editor(|editor, window, cx| {
10977        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10978        let insertion_ranges = editor
10979            .selections
10980            .all(cx)
10981            .iter()
10982            .map(|s| s.range())
10983            .collect::<Vec<_>>();
10984        editor
10985            .insert_snippet(&insertion_ranges, snippet, window, cx)
10986            .unwrap();
10987    });
10988
10989    cx.assert_editor_state(indoc! {"
10990        a.f(«oneˇ», two, «threeˇ») b
10991        a.f(«oneˇ», two, «threeˇ») b
10992        a.f(«oneˇ», two, «threeˇ») b
10993    "});
10994
10995    // Can't move earlier than the first tab stop
10996    cx.update_editor(|editor, window, cx| {
10997        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10998    });
10999    cx.assert_editor_state(indoc! {"
11000        a.f(«oneˇ», two, «threeˇ») b
11001        a.f(«oneˇ», two, «threeˇ») b
11002        a.f(«oneˇ», two, «threeˇ») b
11003    "});
11004
11005    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11006    cx.assert_editor_state(indoc! {"
11007        a.f(one, «twoˇ», three) b
11008        a.f(one, «twoˇ», three) b
11009        a.f(one, «twoˇ», three) b
11010    "});
11011
11012    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11013    cx.assert_editor_state(indoc! {"
11014        a.f(«oneˇ», two, «threeˇ») b
11015        a.f(«oneˇ», two, «threeˇ») b
11016        a.f(«oneˇ», two, «threeˇ») b
11017    "});
11018
11019    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11020    cx.assert_editor_state(indoc! {"
11021        a.f(one, «twoˇ», three) b
11022        a.f(one, «twoˇ», three) b
11023        a.f(one, «twoˇ», three) b
11024    "});
11025    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11026    cx.assert_editor_state(indoc! {"
11027        a.f(one, two, three)ˇ b
11028        a.f(one, two, three)ˇ b
11029        a.f(one, two, three)ˇ b
11030    "});
11031
11032    // As soon as the last tab stop is reached, snippet state is gone
11033    cx.update_editor(|editor, window, cx| {
11034        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11035    });
11036    cx.assert_editor_state(indoc! {"
11037        a.f(one, two, three)ˇ b
11038        a.f(one, two, three)ˇ b
11039        a.f(one, two, three)ˇ b
11040    "});
11041}
11042
11043#[gpui::test]
11044async fn test_snippet_indentation(cx: &mut TestAppContext) {
11045    init_test(cx, |_| {});
11046
11047    let mut cx = EditorTestContext::new(cx).await;
11048
11049    cx.update_editor(|editor, window, cx| {
11050        let snippet = Snippet::parse(indoc! {"
11051            /*
11052             * Multiline comment with leading indentation
11053             *
11054             * $1
11055             */
11056            $0"})
11057        .unwrap();
11058        let insertion_ranges = editor
11059            .selections
11060            .all(cx)
11061            .iter()
11062            .map(|s| s.range())
11063            .collect::<Vec<_>>();
11064        editor
11065            .insert_snippet(&insertion_ranges, snippet, window, cx)
11066            .unwrap();
11067    });
11068
11069    cx.assert_editor_state(indoc! {"
11070        /*
11071         * Multiline comment with leading indentation
11072         *
11073         * ˇ
11074         */
11075    "});
11076
11077    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11078    cx.assert_editor_state(indoc! {"
11079        /*
11080         * Multiline comment with leading indentation
11081         *
11082         *•
11083         */
11084        ˇ"});
11085}
11086
11087#[gpui::test]
11088async fn test_document_format_during_save(cx: &mut TestAppContext) {
11089    init_test(cx, |_| {});
11090
11091    let fs = FakeFs::new(cx.executor());
11092    fs.insert_file(path!("/file.rs"), Default::default()).await;
11093
11094    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11095
11096    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11097    language_registry.add(rust_lang());
11098    let mut fake_servers = language_registry.register_fake_lsp(
11099        "Rust",
11100        FakeLspAdapter {
11101            capabilities: lsp::ServerCapabilities {
11102                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11103                ..Default::default()
11104            },
11105            ..Default::default()
11106        },
11107    );
11108
11109    let buffer = project
11110        .update(cx, |project, cx| {
11111            project.open_local_buffer(path!("/file.rs"), cx)
11112        })
11113        .await
11114        .unwrap();
11115
11116    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11117    let (editor, cx) = cx.add_window_view(|window, cx| {
11118        build_editor_with_project(project.clone(), buffer, window, cx)
11119    });
11120    editor.update_in(cx, |editor, window, cx| {
11121        editor.set_text("one\ntwo\nthree\n", window, cx)
11122    });
11123    assert!(cx.read(|cx| editor.is_dirty(cx)));
11124
11125    cx.executor().start_waiting();
11126    let fake_server = fake_servers.next().await.unwrap();
11127
11128    {
11129        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11130            move |params, _| async move {
11131                assert_eq!(
11132                    params.text_document.uri,
11133                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11134                );
11135                assert_eq!(params.options.tab_size, 4);
11136                Ok(Some(vec![lsp::TextEdit::new(
11137                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11138                    ", ".to_string(),
11139                )]))
11140            },
11141        );
11142        let save = editor
11143            .update_in(cx, |editor, window, cx| {
11144                editor.save(
11145                    SaveOptions {
11146                        format: true,
11147                        autosave: false,
11148                    },
11149                    project.clone(),
11150                    window,
11151                    cx,
11152                )
11153            })
11154            .unwrap();
11155        cx.executor().start_waiting();
11156        save.await;
11157
11158        assert_eq!(
11159            editor.update(cx, |editor, cx| editor.text(cx)),
11160            "one, two\nthree\n"
11161        );
11162        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11163    }
11164
11165    {
11166        editor.update_in(cx, |editor, window, cx| {
11167            editor.set_text("one\ntwo\nthree\n", window, cx)
11168        });
11169        assert!(cx.read(|cx| editor.is_dirty(cx)));
11170
11171        // Ensure we can still save even if formatting hangs.
11172        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11173            move |params, _| async move {
11174                assert_eq!(
11175                    params.text_document.uri,
11176                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11177                );
11178                futures::future::pending::<()>().await;
11179                unreachable!()
11180            },
11181        );
11182        let save = editor
11183            .update_in(cx, |editor, window, cx| {
11184                editor.save(
11185                    SaveOptions {
11186                        format: true,
11187                        autosave: false,
11188                    },
11189                    project.clone(),
11190                    window,
11191                    cx,
11192                )
11193            })
11194            .unwrap();
11195        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11196        cx.executor().start_waiting();
11197        save.await;
11198        assert_eq!(
11199            editor.update(cx, |editor, cx| editor.text(cx)),
11200            "one\ntwo\nthree\n"
11201        );
11202    }
11203
11204    // Set rust language override and assert overridden tabsize is sent to language server
11205    update_test_language_settings(cx, |settings| {
11206        settings.languages.0.insert(
11207            "Rust".into(),
11208            LanguageSettingsContent {
11209                tab_size: NonZeroU32::new(8),
11210                ..Default::default()
11211            },
11212        );
11213    });
11214
11215    {
11216        editor.update_in(cx, |editor, window, cx| {
11217            editor.set_text("somehting_new\n", window, cx)
11218        });
11219        assert!(cx.read(|cx| editor.is_dirty(cx)));
11220        let _formatting_request_signal = fake_server
11221            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11222                assert_eq!(
11223                    params.text_document.uri,
11224                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11225                );
11226                assert_eq!(params.options.tab_size, 8);
11227                Ok(Some(vec![]))
11228            });
11229        let save = editor
11230            .update_in(cx, |editor, window, cx| {
11231                editor.save(
11232                    SaveOptions {
11233                        format: true,
11234                        autosave: false,
11235                    },
11236                    project.clone(),
11237                    window,
11238                    cx,
11239                )
11240            })
11241            .unwrap();
11242        cx.executor().start_waiting();
11243        save.await;
11244    }
11245}
11246
11247#[gpui::test]
11248async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11249    init_test(cx, |settings| {
11250        settings.defaults.ensure_final_newline_on_save = Some(false);
11251    });
11252
11253    let fs = FakeFs::new(cx.executor());
11254    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11255
11256    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11257
11258    let buffer = project
11259        .update(cx, |project, cx| {
11260            project.open_local_buffer(path!("/file.txt"), cx)
11261        })
11262        .await
11263        .unwrap();
11264
11265    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11266    let (editor, cx) = cx.add_window_view(|window, cx| {
11267        build_editor_with_project(project.clone(), buffer, window, cx)
11268    });
11269    editor.update_in(cx, |editor, window, cx| {
11270        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11271            s.select_ranges([0..0])
11272        });
11273    });
11274    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11275
11276    editor.update_in(cx, |editor, window, cx| {
11277        editor.handle_input("\n", window, cx)
11278    });
11279    cx.run_until_parked();
11280    save(&editor, &project, cx).await;
11281    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11282
11283    editor.update_in(cx, |editor, window, cx| {
11284        editor.undo(&Default::default(), window, cx);
11285    });
11286    save(&editor, &project, cx).await;
11287    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11288
11289    editor.update_in(cx, |editor, window, cx| {
11290        editor.redo(&Default::default(), window, cx);
11291    });
11292    cx.run_until_parked();
11293    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11294
11295    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11296        let save = editor
11297            .update_in(cx, |editor, window, cx| {
11298                editor.save(
11299                    SaveOptions {
11300                        format: true,
11301                        autosave: false,
11302                    },
11303                    project.clone(),
11304                    window,
11305                    cx,
11306                )
11307            })
11308            .unwrap();
11309        cx.executor().start_waiting();
11310        save.await;
11311        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11312    }
11313}
11314
11315#[gpui::test]
11316async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11317    init_test(cx, |_| {});
11318
11319    let cols = 4;
11320    let rows = 10;
11321    let sample_text_1 = sample_text(rows, cols, 'a');
11322    assert_eq!(
11323        sample_text_1,
11324        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11325    );
11326    let sample_text_2 = sample_text(rows, cols, 'l');
11327    assert_eq!(
11328        sample_text_2,
11329        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11330    );
11331    let sample_text_3 = sample_text(rows, cols, 'v');
11332    assert_eq!(
11333        sample_text_3,
11334        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11335    );
11336
11337    let fs = FakeFs::new(cx.executor());
11338    fs.insert_tree(
11339        path!("/a"),
11340        json!({
11341            "main.rs": sample_text_1,
11342            "other.rs": sample_text_2,
11343            "lib.rs": sample_text_3,
11344        }),
11345    )
11346    .await;
11347
11348    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11349    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11350    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11351
11352    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11353    language_registry.add(rust_lang());
11354    let mut fake_servers = language_registry.register_fake_lsp(
11355        "Rust",
11356        FakeLspAdapter {
11357            capabilities: lsp::ServerCapabilities {
11358                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11359                ..Default::default()
11360            },
11361            ..Default::default()
11362        },
11363    );
11364
11365    let worktree = project.update(cx, |project, cx| {
11366        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11367        assert_eq!(worktrees.len(), 1);
11368        worktrees.pop().unwrap()
11369    });
11370    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11371
11372    let buffer_1 = project
11373        .update(cx, |project, cx| {
11374            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11375        })
11376        .await
11377        .unwrap();
11378    let buffer_2 = project
11379        .update(cx, |project, cx| {
11380            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11381        })
11382        .await
11383        .unwrap();
11384    let buffer_3 = project
11385        .update(cx, |project, cx| {
11386            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11387        })
11388        .await
11389        .unwrap();
11390
11391    let multi_buffer = cx.new(|cx| {
11392        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11393        multi_buffer.push_excerpts(
11394            buffer_1.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.push_excerpts(
11403            buffer_2.clone(),
11404            [
11405                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11406                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11407                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11408            ],
11409            cx,
11410        );
11411        multi_buffer.push_excerpts(
11412            buffer_3.clone(),
11413            [
11414                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11415                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11416                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11417            ],
11418            cx,
11419        );
11420        multi_buffer
11421    });
11422    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11423        Editor::new(
11424            EditorMode::full(),
11425            multi_buffer,
11426            Some(project.clone()),
11427            window,
11428            cx,
11429        )
11430    });
11431
11432    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11433        editor.change_selections(
11434            SelectionEffects::scroll(Autoscroll::Next),
11435            window,
11436            cx,
11437            |s| s.select_ranges(Some(1..2)),
11438        );
11439        editor.insert("|one|two|three|", window, cx);
11440    });
11441    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11442    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11443        editor.change_selections(
11444            SelectionEffects::scroll(Autoscroll::Next),
11445            window,
11446            cx,
11447            |s| s.select_ranges(Some(60..70)),
11448        );
11449        editor.insert("|four|five|six|", window, cx);
11450    });
11451    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11452
11453    // First two buffers should be edited, but not the third one.
11454    assert_eq!(
11455        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11456        "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}",
11457    );
11458    buffer_1.update(cx, |buffer, _| {
11459        assert!(buffer.is_dirty());
11460        assert_eq!(
11461            buffer.text(),
11462            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11463        )
11464    });
11465    buffer_2.update(cx, |buffer, _| {
11466        assert!(buffer.is_dirty());
11467        assert_eq!(
11468            buffer.text(),
11469            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11470        )
11471    });
11472    buffer_3.update(cx, |buffer, _| {
11473        assert!(!buffer.is_dirty());
11474        assert_eq!(buffer.text(), sample_text_3,)
11475    });
11476    cx.executor().run_until_parked();
11477
11478    cx.executor().start_waiting();
11479    let save = multi_buffer_editor
11480        .update_in(cx, |editor, window, cx| {
11481            editor.save(
11482                SaveOptions {
11483                    format: true,
11484                    autosave: false,
11485                },
11486                project.clone(),
11487                window,
11488                cx,
11489            )
11490        })
11491        .unwrap();
11492
11493    let fake_server = fake_servers.next().await.unwrap();
11494    fake_server
11495        .server
11496        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11497            Ok(Some(vec![lsp::TextEdit::new(
11498                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11499                format!("[{} formatted]", params.text_document.uri),
11500            )]))
11501        })
11502        .detach();
11503    save.await;
11504
11505    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11506    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11507    assert_eq!(
11508        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11509        uri!(
11510            "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}"
11511        ),
11512    );
11513    buffer_1.update(cx, |buffer, _| {
11514        assert!(!buffer.is_dirty());
11515        assert_eq!(
11516            buffer.text(),
11517            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11518        )
11519    });
11520    buffer_2.update(cx, |buffer, _| {
11521        assert!(!buffer.is_dirty());
11522        assert_eq!(
11523            buffer.text(),
11524            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11525        )
11526    });
11527    buffer_3.update(cx, |buffer, _| {
11528        assert!(!buffer.is_dirty());
11529        assert_eq!(buffer.text(), sample_text_3,)
11530    });
11531}
11532
11533#[gpui::test]
11534async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11535    init_test(cx, |_| {});
11536
11537    let fs = FakeFs::new(cx.executor());
11538    fs.insert_tree(
11539        path!("/dir"),
11540        json!({
11541            "file1.rs": "fn main() { println!(\"hello\"); }",
11542            "file2.rs": "fn test() { println!(\"test\"); }",
11543            "file3.rs": "fn other() { println!(\"other\"); }\n",
11544        }),
11545    )
11546    .await;
11547
11548    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11549    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11550    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11551
11552    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11553    language_registry.add(rust_lang());
11554
11555    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11556    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11557
11558    // Open three buffers
11559    let buffer_1 = project
11560        .update(cx, |project, cx| {
11561            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11562        })
11563        .await
11564        .unwrap();
11565    let buffer_2 = project
11566        .update(cx, |project, cx| {
11567            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11568        })
11569        .await
11570        .unwrap();
11571    let buffer_3 = project
11572        .update(cx, |project, cx| {
11573            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11574        })
11575        .await
11576        .unwrap();
11577
11578    // Create a multi-buffer with all three buffers
11579    let multi_buffer = cx.new(|cx| {
11580        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11581        multi_buffer.push_excerpts(
11582            buffer_1.clone(),
11583            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11584            cx,
11585        );
11586        multi_buffer.push_excerpts(
11587            buffer_2.clone(),
11588            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11589            cx,
11590        );
11591        multi_buffer.push_excerpts(
11592            buffer_3.clone(),
11593            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11594            cx,
11595        );
11596        multi_buffer
11597    });
11598
11599    let editor = cx.new_window_entity(|window, cx| {
11600        Editor::new(
11601            EditorMode::full(),
11602            multi_buffer,
11603            Some(project.clone()),
11604            window,
11605            cx,
11606        )
11607    });
11608
11609    // Edit only the first buffer
11610    editor.update_in(cx, |editor, window, cx| {
11611        editor.change_selections(
11612            SelectionEffects::scroll(Autoscroll::Next),
11613            window,
11614            cx,
11615            |s| s.select_ranges(Some(10..10)),
11616        );
11617        editor.insert("// edited", window, cx);
11618    });
11619
11620    // Verify that only buffer 1 is dirty
11621    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11622    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11623    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11624
11625    // Get write counts after file creation (files were created with initial content)
11626    // We expect each file to have been written once during creation
11627    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11628    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11629    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11630
11631    // Perform autosave
11632    let save_task = editor.update_in(cx, |editor, window, cx| {
11633        editor.save(
11634            SaveOptions {
11635                format: true,
11636                autosave: true,
11637            },
11638            project.clone(),
11639            window,
11640            cx,
11641        )
11642    });
11643    save_task.await.unwrap();
11644
11645    // Only the dirty buffer should have been saved
11646    assert_eq!(
11647        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11648        1,
11649        "Buffer 1 was dirty, so it should have been written once during autosave"
11650    );
11651    assert_eq!(
11652        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11653        0,
11654        "Buffer 2 was clean, so it should not have been written during autosave"
11655    );
11656    assert_eq!(
11657        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11658        0,
11659        "Buffer 3 was clean, so it should not have been written during autosave"
11660    );
11661
11662    // Verify buffer states after autosave
11663    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11664    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11665    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11666
11667    // Now perform a manual save (format = true)
11668    let save_task = editor.update_in(cx, |editor, window, cx| {
11669        editor.save(
11670            SaveOptions {
11671                format: true,
11672                autosave: false,
11673            },
11674            project.clone(),
11675            window,
11676            cx,
11677        )
11678    });
11679    save_task.await.unwrap();
11680
11681    // During manual save, clean buffers don't get written to disk
11682    // They just get did_save called for language server notifications
11683    assert_eq!(
11684        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11685        1,
11686        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11687    );
11688    assert_eq!(
11689        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11690        0,
11691        "Buffer 2 should not have been written at all"
11692    );
11693    assert_eq!(
11694        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11695        0,
11696        "Buffer 3 should not have been written at all"
11697    );
11698}
11699
11700async fn setup_range_format_test(
11701    cx: &mut TestAppContext,
11702) -> (
11703    Entity<Project>,
11704    Entity<Editor>,
11705    &mut gpui::VisualTestContext,
11706    lsp::FakeLanguageServer,
11707) {
11708    init_test(cx, |_| {});
11709
11710    let fs = FakeFs::new(cx.executor());
11711    fs.insert_file(path!("/file.rs"), Default::default()).await;
11712
11713    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11714
11715    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11716    language_registry.add(rust_lang());
11717    let mut fake_servers = language_registry.register_fake_lsp(
11718        "Rust",
11719        FakeLspAdapter {
11720            capabilities: lsp::ServerCapabilities {
11721                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11722                ..lsp::ServerCapabilities::default()
11723            },
11724            ..FakeLspAdapter::default()
11725        },
11726    );
11727
11728    let buffer = project
11729        .update(cx, |project, cx| {
11730            project.open_local_buffer(path!("/file.rs"), cx)
11731        })
11732        .await
11733        .unwrap();
11734
11735    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11736    let (editor, cx) = cx.add_window_view(|window, cx| {
11737        build_editor_with_project(project.clone(), buffer, window, cx)
11738    });
11739
11740    cx.executor().start_waiting();
11741    let fake_server = fake_servers.next().await.unwrap();
11742
11743    (project, editor, cx, fake_server)
11744}
11745
11746#[gpui::test]
11747async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11748    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11749
11750    editor.update_in(cx, |editor, window, cx| {
11751        editor.set_text("one\ntwo\nthree\n", window, cx)
11752    });
11753    assert!(cx.read(|cx| editor.is_dirty(cx)));
11754
11755    let save = editor
11756        .update_in(cx, |editor, window, cx| {
11757            editor.save(
11758                SaveOptions {
11759                    format: true,
11760                    autosave: false,
11761                },
11762                project.clone(),
11763                window,
11764                cx,
11765            )
11766        })
11767        .unwrap();
11768    fake_server
11769        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11770            assert_eq!(
11771                params.text_document.uri,
11772                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11773            );
11774            assert_eq!(params.options.tab_size, 4);
11775            Ok(Some(vec![lsp::TextEdit::new(
11776                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11777                ", ".to_string(),
11778            )]))
11779        })
11780        .next()
11781        .await;
11782    cx.executor().start_waiting();
11783    save.await;
11784    assert_eq!(
11785        editor.update(cx, |editor, cx| editor.text(cx)),
11786        "one, two\nthree\n"
11787    );
11788    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11789}
11790
11791#[gpui::test]
11792async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11793    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11794
11795    editor.update_in(cx, |editor, window, cx| {
11796        editor.set_text("one\ntwo\nthree\n", window, cx)
11797    });
11798    assert!(cx.read(|cx| editor.is_dirty(cx)));
11799
11800    // Test that save still works when formatting hangs
11801    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11802        move |params, _| async move {
11803            assert_eq!(
11804                params.text_document.uri,
11805                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11806            );
11807            futures::future::pending::<()>().await;
11808            unreachable!()
11809        },
11810    );
11811    let save = editor
11812        .update_in(cx, |editor, window, cx| {
11813            editor.save(
11814                SaveOptions {
11815                    format: true,
11816                    autosave: false,
11817                },
11818                project.clone(),
11819                window,
11820                cx,
11821            )
11822        })
11823        .unwrap();
11824    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11825    cx.executor().start_waiting();
11826    save.await;
11827    assert_eq!(
11828        editor.update(cx, |editor, cx| editor.text(cx)),
11829        "one\ntwo\nthree\n"
11830    );
11831    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11832}
11833
11834#[gpui::test]
11835async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11836    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11837
11838    // Buffer starts clean, no formatting should be requested
11839    let save = editor
11840        .update_in(cx, |editor, window, cx| {
11841            editor.save(
11842                SaveOptions {
11843                    format: false,
11844                    autosave: false,
11845                },
11846                project.clone(),
11847                window,
11848                cx,
11849            )
11850        })
11851        .unwrap();
11852    let _pending_format_request = fake_server
11853        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11854            panic!("Should not be invoked");
11855        })
11856        .next();
11857    cx.executor().start_waiting();
11858    save.await;
11859    cx.run_until_parked();
11860}
11861
11862#[gpui::test]
11863async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11864    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11865
11866    // Set Rust language override and assert overridden tabsize is sent to language server
11867    update_test_language_settings(cx, |settings| {
11868        settings.languages.0.insert(
11869            "Rust".into(),
11870            LanguageSettingsContent {
11871                tab_size: NonZeroU32::new(8),
11872                ..Default::default()
11873            },
11874        );
11875    });
11876
11877    editor.update_in(cx, |editor, window, cx| {
11878        editor.set_text("something_new\n", window, cx)
11879    });
11880    assert!(cx.read(|cx| editor.is_dirty(cx)));
11881    let save = editor
11882        .update_in(cx, |editor, window, cx| {
11883            editor.save(
11884                SaveOptions {
11885                    format: true,
11886                    autosave: false,
11887                },
11888                project.clone(),
11889                window,
11890                cx,
11891            )
11892        })
11893        .unwrap();
11894    fake_server
11895        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11896            assert_eq!(
11897                params.text_document.uri,
11898                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11899            );
11900            assert_eq!(params.options.tab_size, 8);
11901            Ok(Some(Vec::new()))
11902        })
11903        .next()
11904        .await;
11905    save.await;
11906}
11907
11908#[gpui::test]
11909async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11910    init_test(cx, |settings| {
11911        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11912            Formatter::LanguageServer { name: None },
11913        )))
11914    });
11915
11916    let fs = FakeFs::new(cx.executor());
11917    fs.insert_file(path!("/file.rs"), Default::default()).await;
11918
11919    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11920
11921    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11922    language_registry.add(Arc::new(Language::new(
11923        LanguageConfig {
11924            name: "Rust".into(),
11925            matcher: LanguageMatcher {
11926                path_suffixes: vec!["rs".to_string()],
11927                ..Default::default()
11928            },
11929            ..LanguageConfig::default()
11930        },
11931        Some(tree_sitter_rust::LANGUAGE.into()),
11932    )));
11933    update_test_language_settings(cx, |settings| {
11934        // Enable Prettier formatting for the same buffer, and ensure
11935        // LSP is called instead of Prettier.
11936        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11937    });
11938    let mut fake_servers = language_registry.register_fake_lsp(
11939        "Rust",
11940        FakeLspAdapter {
11941            capabilities: lsp::ServerCapabilities {
11942                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11943                ..Default::default()
11944            },
11945            ..Default::default()
11946        },
11947    );
11948
11949    let buffer = project
11950        .update(cx, |project, cx| {
11951            project.open_local_buffer(path!("/file.rs"), cx)
11952        })
11953        .await
11954        .unwrap();
11955
11956    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11957    let (editor, cx) = cx.add_window_view(|window, cx| {
11958        build_editor_with_project(project.clone(), buffer, window, cx)
11959    });
11960    editor.update_in(cx, |editor, window, cx| {
11961        editor.set_text("one\ntwo\nthree\n", window, cx)
11962    });
11963
11964    cx.executor().start_waiting();
11965    let fake_server = fake_servers.next().await.unwrap();
11966
11967    let format = editor
11968        .update_in(cx, |editor, window, cx| {
11969            editor.perform_format(
11970                project.clone(),
11971                FormatTrigger::Manual,
11972                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11973                window,
11974                cx,
11975            )
11976        })
11977        .unwrap();
11978    fake_server
11979        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11980            assert_eq!(
11981                params.text_document.uri,
11982                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11983            );
11984            assert_eq!(params.options.tab_size, 4);
11985            Ok(Some(vec![lsp::TextEdit::new(
11986                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11987                ", ".to_string(),
11988            )]))
11989        })
11990        .next()
11991        .await;
11992    cx.executor().start_waiting();
11993    format.await;
11994    assert_eq!(
11995        editor.update(cx, |editor, cx| editor.text(cx)),
11996        "one, two\nthree\n"
11997    );
11998
11999    editor.update_in(cx, |editor, window, cx| {
12000        editor.set_text("one\ntwo\nthree\n", window, cx)
12001    });
12002    // Ensure we don't lock if formatting hangs.
12003    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12004        move |params, _| async move {
12005            assert_eq!(
12006                params.text_document.uri,
12007                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12008            );
12009            futures::future::pending::<()>().await;
12010            unreachable!()
12011        },
12012    );
12013    let format = editor
12014        .update_in(cx, |editor, window, cx| {
12015            editor.perform_format(
12016                project,
12017                FormatTrigger::Manual,
12018                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12019                window,
12020                cx,
12021            )
12022        })
12023        .unwrap();
12024    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12025    cx.executor().start_waiting();
12026    format.await;
12027    assert_eq!(
12028        editor.update(cx, |editor, cx| editor.text(cx)),
12029        "one\ntwo\nthree\n"
12030    );
12031}
12032
12033#[gpui::test]
12034async fn test_multiple_formatters(cx: &mut TestAppContext) {
12035    init_test(cx, |settings| {
12036        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12037        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12038            Formatter::LanguageServer { name: None },
12039            Formatter::CodeAction("code-action-1".into()),
12040            Formatter::CodeAction("code-action-2".into()),
12041        ])))
12042    });
12043
12044    let fs = FakeFs::new(cx.executor());
12045    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12046        .await;
12047
12048    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12049    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12050    language_registry.add(rust_lang());
12051
12052    let mut fake_servers = language_registry.register_fake_lsp(
12053        "Rust",
12054        FakeLspAdapter {
12055            capabilities: lsp::ServerCapabilities {
12056                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12057                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12058                    commands: vec!["the-command-for-code-action-1".into()],
12059                    ..Default::default()
12060                }),
12061                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12062                ..Default::default()
12063            },
12064            ..Default::default()
12065        },
12066    );
12067
12068    let buffer = project
12069        .update(cx, |project, cx| {
12070            project.open_local_buffer(path!("/file.rs"), cx)
12071        })
12072        .await
12073        .unwrap();
12074
12075    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12076    let (editor, cx) = cx.add_window_view(|window, cx| {
12077        build_editor_with_project(project.clone(), buffer, window, cx)
12078    });
12079
12080    cx.executor().start_waiting();
12081
12082    let fake_server = fake_servers.next().await.unwrap();
12083    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12084        move |_params, _| async move {
12085            Ok(Some(vec![lsp::TextEdit::new(
12086                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12087                "applied-formatting\n".to_string(),
12088            )]))
12089        },
12090    );
12091    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12092        move |params, _| async move {
12093            let requested_code_actions = params.context.only.expect("Expected code action request");
12094            assert_eq!(requested_code_actions.len(), 1);
12095
12096            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12097            let code_action = match requested_code_actions[0].as_str() {
12098                "code-action-1" => lsp::CodeAction {
12099                    kind: Some("code-action-1".into()),
12100                    edit: Some(lsp::WorkspaceEdit::new(
12101                        [(
12102                            uri,
12103                            vec![lsp::TextEdit::new(
12104                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12105                                "applied-code-action-1-edit\n".to_string(),
12106                            )],
12107                        )]
12108                        .into_iter()
12109                        .collect(),
12110                    )),
12111                    command: Some(lsp::Command {
12112                        command: "the-command-for-code-action-1".into(),
12113                        ..Default::default()
12114                    }),
12115                    ..Default::default()
12116                },
12117                "code-action-2" => lsp::CodeAction {
12118                    kind: Some("code-action-2".into()),
12119                    edit: Some(lsp::WorkspaceEdit::new(
12120                        [(
12121                            uri,
12122                            vec![lsp::TextEdit::new(
12123                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12124                                "applied-code-action-2-edit\n".to_string(),
12125                            )],
12126                        )]
12127                        .into_iter()
12128                        .collect(),
12129                    )),
12130                    ..Default::default()
12131                },
12132                req => panic!("Unexpected code action request: {:?}", req),
12133            };
12134            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12135                code_action,
12136            )]))
12137        },
12138    );
12139
12140    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12141        move |params, _| async move { Ok(params) }
12142    });
12143
12144    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12145    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12146        let fake = fake_server.clone();
12147        let lock = command_lock.clone();
12148        move |params, _| {
12149            assert_eq!(params.command, "the-command-for-code-action-1");
12150            let fake = fake.clone();
12151            let lock = lock.clone();
12152            async move {
12153                lock.lock().await;
12154                fake.server
12155                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12156                        label: None,
12157                        edit: lsp::WorkspaceEdit {
12158                            changes: Some(
12159                                [(
12160                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12161                                    vec![lsp::TextEdit {
12162                                        range: lsp::Range::new(
12163                                            lsp::Position::new(0, 0),
12164                                            lsp::Position::new(0, 0),
12165                                        ),
12166                                        new_text: "applied-code-action-1-command\n".into(),
12167                                    }],
12168                                )]
12169                                .into_iter()
12170                                .collect(),
12171                            ),
12172                            ..Default::default()
12173                        },
12174                    })
12175                    .await
12176                    .into_response()
12177                    .unwrap();
12178                Ok(Some(json!(null)))
12179            }
12180        }
12181    });
12182
12183    cx.executor().start_waiting();
12184    editor
12185        .update_in(cx, |editor, window, cx| {
12186            editor.perform_format(
12187                project.clone(),
12188                FormatTrigger::Manual,
12189                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12190                window,
12191                cx,
12192            )
12193        })
12194        .unwrap()
12195        .await;
12196    editor.update(cx, |editor, cx| {
12197        assert_eq!(
12198            editor.text(cx),
12199            r#"
12200                applied-code-action-2-edit
12201                applied-code-action-1-command
12202                applied-code-action-1-edit
12203                applied-formatting
12204                one
12205                two
12206                three
12207            "#
12208            .unindent()
12209        );
12210    });
12211
12212    editor.update_in(cx, |editor, window, cx| {
12213        editor.undo(&Default::default(), window, cx);
12214        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12215    });
12216
12217    // Perform a manual edit while waiting for an LSP command
12218    // that's being run as part of a formatting code action.
12219    let lock_guard = command_lock.lock().await;
12220    let format = editor
12221        .update_in(cx, |editor, window, cx| {
12222            editor.perform_format(
12223                project.clone(),
12224                FormatTrigger::Manual,
12225                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12226                window,
12227                cx,
12228            )
12229        })
12230        .unwrap();
12231    cx.run_until_parked();
12232    editor.update(cx, |editor, cx| {
12233        assert_eq!(
12234            editor.text(cx),
12235            r#"
12236                applied-code-action-1-edit
12237                applied-formatting
12238                one
12239                two
12240                three
12241            "#
12242            .unindent()
12243        );
12244
12245        editor.buffer.update(cx, |buffer, cx| {
12246            let ix = buffer.len(cx);
12247            buffer.edit([(ix..ix, "edited\n")], None, cx);
12248        });
12249    });
12250
12251    // Allow the LSP command to proceed. Because the buffer was edited,
12252    // the second code action will not be run.
12253    drop(lock_guard);
12254    format.await;
12255    editor.update_in(cx, |editor, window, cx| {
12256        assert_eq!(
12257            editor.text(cx),
12258            r#"
12259                applied-code-action-1-command
12260                applied-code-action-1-edit
12261                applied-formatting
12262                one
12263                two
12264                three
12265                edited
12266            "#
12267            .unindent()
12268        );
12269
12270        // The manual edit is undone first, because it is the last thing the user did
12271        // (even though the command completed afterwards).
12272        editor.undo(&Default::default(), window, cx);
12273        assert_eq!(
12274            editor.text(cx),
12275            r#"
12276                applied-code-action-1-command
12277                applied-code-action-1-edit
12278                applied-formatting
12279                one
12280                two
12281                three
12282            "#
12283            .unindent()
12284        );
12285
12286        // All the formatting (including the command, which completed after the manual edit)
12287        // is undone together.
12288        editor.undo(&Default::default(), window, cx);
12289        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12290    });
12291}
12292
12293#[gpui::test]
12294async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12295    init_test(cx, |settings| {
12296        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12297            Formatter::LanguageServer { name: None },
12298        ])))
12299    });
12300
12301    let fs = FakeFs::new(cx.executor());
12302    fs.insert_file(path!("/file.ts"), Default::default()).await;
12303
12304    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12305
12306    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12307    language_registry.add(Arc::new(Language::new(
12308        LanguageConfig {
12309            name: "TypeScript".into(),
12310            matcher: LanguageMatcher {
12311                path_suffixes: vec!["ts".to_string()],
12312                ..Default::default()
12313            },
12314            ..LanguageConfig::default()
12315        },
12316        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12317    )));
12318    update_test_language_settings(cx, |settings| {
12319        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12320    });
12321    let mut fake_servers = language_registry.register_fake_lsp(
12322        "TypeScript",
12323        FakeLspAdapter {
12324            capabilities: lsp::ServerCapabilities {
12325                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12326                ..Default::default()
12327            },
12328            ..Default::default()
12329        },
12330    );
12331
12332    let buffer = project
12333        .update(cx, |project, cx| {
12334            project.open_local_buffer(path!("/file.ts"), cx)
12335        })
12336        .await
12337        .unwrap();
12338
12339    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12340    let (editor, cx) = cx.add_window_view(|window, cx| {
12341        build_editor_with_project(project.clone(), buffer, window, cx)
12342    });
12343    editor.update_in(cx, |editor, window, cx| {
12344        editor.set_text(
12345            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12346            window,
12347            cx,
12348        )
12349    });
12350
12351    cx.executor().start_waiting();
12352    let fake_server = fake_servers.next().await.unwrap();
12353
12354    let format = editor
12355        .update_in(cx, |editor, window, cx| {
12356            editor.perform_code_action_kind(
12357                project.clone(),
12358                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12359                window,
12360                cx,
12361            )
12362        })
12363        .unwrap();
12364    fake_server
12365        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12366            assert_eq!(
12367                params.text_document.uri,
12368                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12369            );
12370            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12371                lsp::CodeAction {
12372                    title: "Organize Imports".to_string(),
12373                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12374                    edit: Some(lsp::WorkspaceEdit {
12375                        changes: Some(
12376                            [(
12377                                params.text_document.uri.clone(),
12378                                vec![lsp::TextEdit::new(
12379                                    lsp::Range::new(
12380                                        lsp::Position::new(1, 0),
12381                                        lsp::Position::new(2, 0),
12382                                    ),
12383                                    "".to_string(),
12384                                )],
12385                            )]
12386                            .into_iter()
12387                            .collect(),
12388                        ),
12389                        ..Default::default()
12390                    }),
12391                    ..Default::default()
12392                },
12393            )]))
12394        })
12395        .next()
12396        .await;
12397    cx.executor().start_waiting();
12398    format.await;
12399    assert_eq!(
12400        editor.update(cx, |editor, cx| editor.text(cx)),
12401        "import { a } from 'module';\n\nconst x = a;\n"
12402    );
12403
12404    editor.update_in(cx, |editor, window, cx| {
12405        editor.set_text(
12406            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12407            window,
12408            cx,
12409        )
12410    });
12411    // Ensure we don't lock if code action hangs.
12412    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12413        move |params, _| async move {
12414            assert_eq!(
12415                params.text_document.uri,
12416                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12417            );
12418            futures::future::pending::<()>().await;
12419            unreachable!()
12420        },
12421    );
12422    let format = editor
12423        .update_in(cx, |editor, window, cx| {
12424            editor.perform_code_action_kind(
12425                project,
12426                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12427                window,
12428                cx,
12429            )
12430        })
12431        .unwrap();
12432    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12433    cx.executor().start_waiting();
12434    format.await;
12435    assert_eq!(
12436        editor.update(cx, |editor, cx| editor.text(cx)),
12437        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12438    );
12439}
12440
12441#[gpui::test]
12442async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12443    init_test(cx, |_| {});
12444
12445    let mut cx = EditorLspTestContext::new_rust(
12446        lsp::ServerCapabilities {
12447            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12448            ..Default::default()
12449        },
12450        cx,
12451    )
12452    .await;
12453
12454    cx.set_state(indoc! {"
12455        one.twoˇ
12456    "});
12457
12458    // The format request takes a long time. When it completes, it inserts
12459    // a newline and an indent before the `.`
12460    cx.lsp
12461        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12462            let executor = cx.background_executor().clone();
12463            async move {
12464                executor.timer(Duration::from_millis(100)).await;
12465                Ok(Some(vec![lsp::TextEdit {
12466                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12467                    new_text: "\n    ".into(),
12468                }]))
12469            }
12470        });
12471
12472    // Submit a format request.
12473    let format_1 = cx
12474        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12475        .unwrap();
12476    cx.executor().run_until_parked();
12477
12478    // Submit a second format request.
12479    let format_2 = cx
12480        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12481        .unwrap();
12482    cx.executor().run_until_parked();
12483
12484    // Wait for both format requests to complete
12485    cx.executor().advance_clock(Duration::from_millis(200));
12486    cx.executor().start_waiting();
12487    format_1.await.unwrap();
12488    cx.executor().start_waiting();
12489    format_2.await.unwrap();
12490
12491    // The formatting edits only happens once.
12492    cx.assert_editor_state(indoc! {"
12493        one
12494            .twoˇ
12495    "});
12496}
12497
12498#[gpui::test]
12499async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12500    init_test(cx, |settings| {
12501        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12502    });
12503
12504    let mut cx = EditorLspTestContext::new_rust(
12505        lsp::ServerCapabilities {
12506            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12507            ..Default::default()
12508        },
12509        cx,
12510    )
12511    .await;
12512
12513    // Set up a buffer white some trailing whitespace and no trailing newline.
12514    cx.set_state(
12515        &[
12516            "one ",   //
12517            "twoˇ",   //
12518            "three ", //
12519            "four",   //
12520        ]
12521        .join("\n"),
12522    );
12523
12524    // Record which buffer changes have been sent to the language server
12525    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12526    cx.lsp
12527        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12528            let buffer_changes = buffer_changes.clone();
12529            move |params, _| {
12530                buffer_changes.lock().extend(
12531                    params
12532                        .content_changes
12533                        .into_iter()
12534                        .map(|e| (e.range.unwrap(), e.text)),
12535                );
12536            }
12537        });
12538
12539    // Handle formatting requests to the language server.
12540    cx.lsp
12541        .set_request_handler::<lsp::request::Formatting, _, _>({
12542            let buffer_changes = buffer_changes.clone();
12543            move |_, _| {
12544                let buffer_changes = buffer_changes.clone();
12545                // Insert blank lines between each line of the buffer.
12546                async move {
12547                    // When formatting is requested, trailing whitespace has already been stripped,
12548                    // and the trailing newline has already been added.
12549                    assert_eq!(
12550                        &buffer_changes.lock()[1..],
12551                        &[
12552                            (
12553                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12554                                "".into()
12555                            ),
12556                            (
12557                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12558                                "".into()
12559                            ),
12560                            (
12561                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12562                                "\n".into()
12563                            ),
12564                        ]
12565                    );
12566
12567                    Ok(Some(vec![
12568                        lsp::TextEdit {
12569                            range: lsp::Range::new(
12570                                lsp::Position::new(1, 0),
12571                                lsp::Position::new(1, 0),
12572                            ),
12573                            new_text: "\n".into(),
12574                        },
12575                        lsp::TextEdit {
12576                            range: lsp::Range::new(
12577                                lsp::Position::new(2, 0),
12578                                lsp::Position::new(2, 0),
12579                            ),
12580                            new_text: "\n".into(),
12581                        },
12582                    ]))
12583                }
12584            }
12585        });
12586
12587    // Submit a format request.
12588    let format = cx
12589        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12590        .unwrap();
12591
12592    cx.run_until_parked();
12593    // After formatting the buffer, the trailing whitespace is stripped,
12594    // a newline is appended, and the edits provided by the language server
12595    // have been applied.
12596    format.await.unwrap();
12597
12598    cx.assert_editor_state(
12599        &[
12600            "one",   //
12601            "",      //
12602            "twoˇ",  //
12603            "",      //
12604            "three", //
12605            "four",  //
12606            "",      //
12607        ]
12608        .join("\n"),
12609    );
12610
12611    // Undoing the formatting undoes the trailing whitespace removal, the
12612    // trailing newline, and the LSP edits.
12613    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12614    cx.assert_editor_state(
12615        &[
12616            "one ",   //
12617            "twoˇ",   //
12618            "three ", //
12619            "four",   //
12620        ]
12621        .join("\n"),
12622    );
12623}
12624
12625#[gpui::test]
12626async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12627    cx: &mut TestAppContext,
12628) {
12629    init_test(cx, |_| {});
12630
12631    cx.update(|cx| {
12632        cx.update_global::<SettingsStore, _>(|settings, cx| {
12633            settings.update_user_settings(cx, |settings| {
12634                settings.editor.auto_signature_help = Some(true);
12635            });
12636        });
12637    });
12638
12639    let mut cx = EditorLspTestContext::new_rust(
12640        lsp::ServerCapabilities {
12641            signature_help_provider: Some(lsp::SignatureHelpOptions {
12642                ..Default::default()
12643            }),
12644            ..Default::default()
12645        },
12646        cx,
12647    )
12648    .await;
12649
12650    let language = Language::new(
12651        LanguageConfig {
12652            name: "Rust".into(),
12653            brackets: BracketPairConfig {
12654                pairs: vec![
12655                    BracketPair {
12656                        start: "{".to_string(),
12657                        end: "}".to_string(),
12658                        close: true,
12659                        surround: true,
12660                        newline: true,
12661                    },
12662                    BracketPair {
12663                        start: "(".to_string(),
12664                        end: ")".to_string(),
12665                        close: true,
12666                        surround: true,
12667                        newline: true,
12668                    },
12669                    BracketPair {
12670                        start: "/*".to_string(),
12671                        end: " */".to_string(),
12672                        close: true,
12673                        surround: true,
12674                        newline: true,
12675                    },
12676                    BracketPair {
12677                        start: "[".to_string(),
12678                        end: "]".to_string(),
12679                        close: false,
12680                        surround: false,
12681                        newline: true,
12682                    },
12683                    BracketPair {
12684                        start: "\"".to_string(),
12685                        end: "\"".to_string(),
12686                        close: true,
12687                        surround: true,
12688                        newline: false,
12689                    },
12690                    BracketPair {
12691                        start: "<".to_string(),
12692                        end: ">".to_string(),
12693                        close: false,
12694                        surround: true,
12695                        newline: true,
12696                    },
12697                ],
12698                ..Default::default()
12699            },
12700            autoclose_before: "})]".to_string(),
12701            ..Default::default()
12702        },
12703        Some(tree_sitter_rust::LANGUAGE.into()),
12704    );
12705    let language = Arc::new(language);
12706
12707    cx.language_registry().add(language.clone());
12708    cx.update_buffer(|buffer, cx| {
12709        buffer.set_language(Some(language), cx);
12710    });
12711
12712    cx.set_state(
12713        &r#"
12714            fn main() {
12715                sampleˇ
12716            }
12717        "#
12718        .unindent(),
12719    );
12720
12721    cx.update_editor(|editor, window, cx| {
12722        editor.handle_input("(", window, cx);
12723    });
12724    cx.assert_editor_state(
12725        &"
12726            fn main() {
12727                sample(ˇ)
12728            }
12729        "
12730        .unindent(),
12731    );
12732
12733    let mocked_response = lsp::SignatureHelp {
12734        signatures: vec![lsp::SignatureInformation {
12735            label: "fn sample(param1: u8, param2: u8)".to_string(),
12736            documentation: None,
12737            parameters: Some(vec![
12738                lsp::ParameterInformation {
12739                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12740                    documentation: None,
12741                },
12742                lsp::ParameterInformation {
12743                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12744                    documentation: None,
12745                },
12746            ]),
12747            active_parameter: None,
12748        }],
12749        active_signature: Some(0),
12750        active_parameter: Some(0),
12751    };
12752    handle_signature_help_request(&mut cx, mocked_response).await;
12753
12754    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12755        .await;
12756
12757    cx.editor(|editor, _, _| {
12758        let signature_help_state = editor.signature_help_state.popover().cloned();
12759        let signature = signature_help_state.unwrap();
12760        assert_eq!(
12761            signature.signatures[signature.current_signature].label,
12762            "fn sample(param1: u8, param2: u8)"
12763        );
12764    });
12765}
12766
12767#[gpui::test]
12768async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12769    init_test(cx, |_| {});
12770
12771    cx.update(|cx| {
12772        cx.update_global::<SettingsStore, _>(|settings, cx| {
12773            settings.update_user_settings(cx, |settings| {
12774                settings.editor.auto_signature_help = Some(false);
12775                settings.editor.show_signature_help_after_edits = Some(false);
12776            });
12777        });
12778    });
12779
12780    let mut cx = EditorLspTestContext::new_rust(
12781        lsp::ServerCapabilities {
12782            signature_help_provider: Some(lsp::SignatureHelpOptions {
12783                ..Default::default()
12784            }),
12785            ..Default::default()
12786        },
12787        cx,
12788    )
12789    .await;
12790
12791    let language = Language::new(
12792        LanguageConfig {
12793            name: "Rust".into(),
12794            brackets: BracketPairConfig {
12795                pairs: vec![
12796                    BracketPair {
12797                        start: "{".to_string(),
12798                        end: "}".to_string(),
12799                        close: true,
12800                        surround: true,
12801                        newline: true,
12802                    },
12803                    BracketPair {
12804                        start: "(".to_string(),
12805                        end: ")".to_string(),
12806                        close: true,
12807                        surround: true,
12808                        newline: true,
12809                    },
12810                    BracketPair {
12811                        start: "/*".to_string(),
12812                        end: " */".to_string(),
12813                        close: true,
12814                        surround: true,
12815                        newline: true,
12816                    },
12817                    BracketPair {
12818                        start: "[".to_string(),
12819                        end: "]".to_string(),
12820                        close: false,
12821                        surround: false,
12822                        newline: true,
12823                    },
12824                    BracketPair {
12825                        start: "\"".to_string(),
12826                        end: "\"".to_string(),
12827                        close: true,
12828                        surround: true,
12829                        newline: false,
12830                    },
12831                    BracketPair {
12832                        start: "<".to_string(),
12833                        end: ">".to_string(),
12834                        close: false,
12835                        surround: true,
12836                        newline: true,
12837                    },
12838                ],
12839                ..Default::default()
12840            },
12841            autoclose_before: "})]".to_string(),
12842            ..Default::default()
12843        },
12844        Some(tree_sitter_rust::LANGUAGE.into()),
12845    );
12846    let language = Arc::new(language);
12847
12848    cx.language_registry().add(language.clone());
12849    cx.update_buffer(|buffer, cx| {
12850        buffer.set_language(Some(language), cx);
12851    });
12852
12853    // Ensure that signature_help is not called when no signature help is enabled.
12854    cx.set_state(
12855        &r#"
12856            fn main() {
12857                sampleˇ
12858            }
12859        "#
12860        .unindent(),
12861    );
12862    cx.update_editor(|editor, window, cx| {
12863        editor.handle_input("(", window, cx);
12864    });
12865    cx.assert_editor_state(
12866        &"
12867            fn main() {
12868                sample(ˇ)
12869            }
12870        "
12871        .unindent(),
12872    );
12873    cx.editor(|editor, _, _| {
12874        assert!(editor.signature_help_state.task().is_none());
12875    });
12876
12877    let mocked_response = lsp::SignatureHelp {
12878        signatures: vec![lsp::SignatureInformation {
12879            label: "fn sample(param1: u8, param2: u8)".to_string(),
12880            documentation: None,
12881            parameters: Some(vec![
12882                lsp::ParameterInformation {
12883                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12884                    documentation: None,
12885                },
12886                lsp::ParameterInformation {
12887                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12888                    documentation: None,
12889                },
12890            ]),
12891            active_parameter: None,
12892        }],
12893        active_signature: Some(0),
12894        active_parameter: Some(0),
12895    };
12896
12897    // Ensure that signature_help is called when enabled afte edits
12898    cx.update(|_, cx| {
12899        cx.update_global::<SettingsStore, _>(|settings, cx| {
12900            settings.update_user_settings(cx, |settings| {
12901                settings.editor.auto_signature_help = Some(false);
12902                settings.editor.show_signature_help_after_edits = Some(true);
12903            });
12904        });
12905    });
12906    cx.set_state(
12907        &r#"
12908            fn main() {
12909                sampleˇ
12910            }
12911        "#
12912        .unindent(),
12913    );
12914    cx.update_editor(|editor, window, cx| {
12915        editor.handle_input("(", window, cx);
12916    });
12917    cx.assert_editor_state(
12918        &"
12919            fn main() {
12920                sample(ˇ)
12921            }
12922        "
12923        .unindent(),
12924    );
12925    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12926    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12927        .await;
12928    cx.update_editor(|editor, _, _| {
12929        let signature_help_state = editor.signature_help_state.popover().cloned();
12930        assert!(signature_help_state.is_some());
12931        let signature = signature_help_state.unwrap();
12932        assert_eq!(
12933            signature.signatures[signature.current_signature].label,
12934            "fn sample(param1: u8, param2: u8)"
12935        );
12936        editor.signature_help_state = SignatureHelpState::default();
12937    });
12938
12939    // Ensure that signature_help is called when auto signature help override is enabled
12940    cx.update(|_, cx| {
12941        cx.update_global::<SettingsStore, _>(|settings, cx| {
12942            settings.update_user_settings(cx, |settings| {
12943                settings.editor.auto_signature_help = Some(true);
12944                settings.editor.show_signature_help_after_edits = Some(false);
12945            });
12946        });
12947    });
12948    cx.set_state(
12949        &r#"
12950            fn main() {
12951                sampleˇ
12952            }
12953        "#
12954        .unindent(),
12955    );
12956    cx.update_editor(|editor, window, cx| {
12957        editor.handle_input("(", window, cx);
12958    });
12959    cx.assert_editor_state(
12960        &"
12961            fn main() {
12962                sample(ˇ)
12963            }
12964        "
12965        .unindent(),
12966    );
12967    handle_signature_help_request(&mut cx, mocked_response).await;
12968    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12969        .await;
12970    cx.editor(|editor, _, _| {
12971        let signature_help_state = editor.signature_help_state.popover().cloned();
12972        assert!(signature_help_state.is_some());
12973        let signature = signature_help_state.unwrap();
12974        assert_eq!(
12975            signature.signatures[signature.current_signature].label,
12976            "fn sample(param1: u8, param2: u8)"
12977        );
12978    });
12979}
12980
12981#[gpui::test]
12982async fn test_signature_help(cx: &mut TestAppContext) {
12983    init_test(cx, |_| {});
12984    cx.update(|cx| {
12985        cx.update_global::<SettingsStore, _>(|settings, cx| {
12986            settings.update_user_settings(cx, |settings| {
12987                settings.editor.auto_signature_help = Some(true);
12988            });
12989        });
12990    });
12991
12992    let mut cx = EditorLspTestContext::new_rust(
12993        lsp::ServerCapabilities {
12994            signature_help_provider: Some(lsp::SignatureHelpOptions {
12995                ..Default::default()
12996            }),
12997            ..Default::default()
12998        },
12999        cx,
13000    )
13001    .await;
13002
13003    // A test that directly calls `show_signature_help`
13004    cx.update_editor(|editor, window, cx| {
13005        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13006    });
13007
13008    let mocked_response = lsp::SignatureHelp {
13009        signatures: vec![lsp::SignatureInformation {
13010            label: "fn sample(param1: u8, param2: u8)".to_string(),
13011            documentation: None,
13012            parameters: Some(vec![
13013                lsp::ParameterInformation {
13014                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13015                    documentation: None,
13016                },
13017                lsp::ParameterInformation {
13018                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13019                    documentation: None,
13020                },
13021            ]),
13022            active_parameter: None,
13023        }],
13024        active_signature: Some(0),
13025        active_parameter: Some(0),
13026    };
13027    handle_signature_help_request(&mut cx, mocked_response).await;
13028
13029    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13030        .await;
13031
13032    cx.editor(|editor, _, _| {
13033        let signature_help_state = editor.signature_help_state.popover().cloned();
13034        assert!(signature_help_state.is_some());
13035        let signature = signature_help_state.unwrap();
13036        assert_eq!(
13037            signature.signatures[signature.current_signature].label,
13038            "fn sample(param1: u8, param2: u8)"
13039        );
13040    });
13041
13042    // When exiting outside from inside the brackets, `signature_help` is closed.
13043    cx.set_state(indoc! {"
13044        fn main() {
13045            sample(ˇ);
13046        }
13047
13048        fn sample(param1: u8, param2: u8) {}
13049    "});
13050
13051    cx.update_editor(|editor, window, cx| {
13052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13053            s.select_ranges([0..0])
13054        });
13055    });
13056
13057    let mocked_response = lsp::SignatureHelp {
13058        signatures: Vec::new(),
13059        active_signature: None,
13060        active_parameter: None,
13061    };
13062    handle_signature_help_request(&mut cx, mocked_response).await;
13063
13064    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13065        .await;
13066
13067    cx.editor(|editor, _, _| {
13068        assert!(!editor.signature_help_state.is_shown());
13069    });
13070
13071    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13072    cx.set_state(indoc! {"
13073        fn main() {
13074            sample(ˇ);
13075        }
13076
13077        fn sample(param1: u8, param2: u8) {}
13078    "});
13079
13080    let mocked_response = lsp::SignatureHelp {
13081        signatures: vec![lsp::SignatureInformation {
13082            label: "fn sample(param1: u8, param2: u8)".to_string(),
13083            documentation: None,
13084            parameters: Some(vec![
13085                lsp::ParameterInformation {
13086                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13087                    documentation: None,
13088                },
13089                lsp::ParameterInformation {
13090                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13091                    documentation: None,
13092                },
13093            ]),
13094            active_parameter: None,
13095        }],
13096        active_signature: Some(0),
13097        active_parameter: Some(0),
13098    };
13099    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13100    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13101        .await;
13102    cx.editor(|editor, _, _| {
13103        assert!(editor.signature_help_state.is_shown());
13104    });
13105
13106    // Restore the popover with more parameter input
13107    cx.set_state(indoc! {"
13108        fn main() {
13109            sample(param1, param2ˇ);
13110        }
13111
13112        fn sample(param1: u8, param2: u8) {}
13113    "});
13114
13115    let mocked_response = lsp::SignatureHelp {
13116        signatures: vec![lsp::SignatureInformation {
13117            label: "fn sample(param1: u8, param2: u8)".to_string(),
13118            documentation: None,
13119            parameters: Some(vec![
13120                lsp::ParameterInformation {
13121                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13122                    documentation: None,
13123                },
13124                lsp::ParameterInformation {
13125                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13126                    documentation: None,
13127                },
13128            ]),
13129            active_parameter: None,
13130        }],
13131        active_signature: Some(0),
13132        active_parameter: Some(1),
13133    };
13134    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13135    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13136        .await;
13137
13138    // When selecting a range, the popover is gone.
13139    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13140    cx.update_editor(|editor, window, cx| {
13141        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13142            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13143        })
13144    });
13145    cx.assert_editor_state(indoc! {"
13146        fn main() {
13147            sample(param1, «ˇparam2»);
13148        }
13149
13150        fn sample(param1: u8, param2: u8) {}
13151    "});
13152    cx.editor(|editor, _, _| {
13153        assert!(!editor.signature_help_state.is_shown());
13154    });
13155
13156    // When unselecting again, the popover is back if within the brackets.
13157    cx.update_editor(|editor, window, cx| {
13158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13159            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13160        })
13161    });
13162    cx.assert_editor_state(indoc! {"
13163        fn main() {
13164            sample(param1, ˇparam2);
13165        }
13166
13167        fn sample(param1: u8, param2: u8) {}
13168    "});
13169    handle_signature_help_request(&mut cx, mocked_response).await;
13170    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13171        .await;
13172    cx.editor(|editor, _, _| {
13173        assert!(editor.signature_help_state.is_shown());
13174    });
13175
13176    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13177    cx.update_editor(|editor, window, cx| {
13178        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13179            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13180            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13181        })
13182    });
13183    cx.assert_editor_state(indoc! {"
13184        fn main() {
13185            sample(param1, ˇparam2);
13186        }
13187
13188        fn sample(param1: u8, param2: u8) {}
13189    "});
13190
13191    let mocked_response = lsp::SignatureHelp {
13192        signatures: vec![lsp::SignatureInformation {
13193            label: "fn sample(param1: u8, param2: u8)".to_string(),
13194            documentation: None,
13195            parameters: Some(vec![
13196                lsp::ParameterInformation {
13197                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13198                    documentation: None,
13199                },
13200                lsp::ParameterInformation {
13201                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13202                    documentation: None,
13203                },
13204            ]),
13205            active_parameter: None,
13206        }],
13207        active_signature: Some(0),
13208        active_parameter: Some(1),
13209    };
13210    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13211    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13212        .await;
13213    cx.update_editor(|editor, _, cx| {
13214        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13215    });
13216    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13217        .await;
13218    cx.update_editor(|editor, window, cx| {
13219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13220            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13221        })
13222    });
13223    cx.assert_editor_state(indoc! {"
13224        fn main() {
13225            sample(param1, «ˇparam2»);
13226        }
13227
13228        fn sample(param1: u8, param2: u8) {}
13229    "});
13230    cx.update_editor(|editor, window, cx| {
13231        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13232            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13233        })
13234    });
13235    cx.assert_editor_state(indoc! {"
13236        fn main() {
13237            sample(param1, ˇparam2);
13238        }
13239
13240        fn sample(param1: u8, param2: u8) {}
13241    "});
13242    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13243        .await;
13244}
13245
13246#[gpui::test]
13247async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13248    init_test(cx, |_| {});
13249
13250    let mut cx = EditorLspTestContext::new_rust(
13251        lsp::ServerCapabilities {
13252            signature_help_provider: Some(lsp::SignatureHelpOptions {
13253                ..Default::default()
13254            }),
13255            ..Default::default()
13256        },
13257        cx,
13258    )
13259    .await;
13260
13261    cx.set_state(indoc! {"
13262        fn main() {
13263            overloadedˇ
13264        }
13265    "});
13266
13267    cx.update_editor(|editor, window, cx| {
13268        editor.handle_input("(", window, cx);
13269        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13270    });
13271
13272    // Mock response with 3 signatures
13273    let mocked_response = lsp::SignatureHelp {
13274        signatures: vec![
13275            lsp::SignatureInformation {
13276                label: "fn overloaded(x: i32)".to_string(),
13277                documentation: None,
13278                parameters: Some(vec![lsp::ParameterInformation {
13279                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13280                    documentation: None,
13281                }]),
13282                active_parameter: None,
13283            },
13284            lsp::SignatureInformation {
13285                label: "fn overloaded(x: i32, y: i32)".to_string(),
13286                documentation: None,
13287                parameters: Some(vec![
13288                    lsp::ParameterInformation {
13289                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13290                        documentation: None,
13291                    },
13292                    lsp::ParameterInformation {
13293                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13294                        documentation: None,
13295                    },
13296                ]),
13297                active_parameter: None,
13298            },
13299            lsp::SignatureInformation {
13300                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13301                documentation: None,
13302                parameters: Some(vec![
13303                    lsp::ParameterInformation {
13304                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13305                        documentation: None,
13306                    },
13307                    lsp::ParameterInformation {
13308                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13309                        documentation: None,
13310                    },
13311                    lsp::ParameterInformation {
13312                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13313                        documentation: None,
13314                    },
13315                ]),
13316                active_parameter: None,
13317            },
13318        ],
13319        active_signature: Some(1),
13320        active_parameter: Some(0),
13321    };
13322    handle_signature_help_request(&mut cx, mocked_response).await;
13323
13324    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13325        .await;
13326
13327    // Verify we have multiple signatures and the right one is selected
13328    cx.editor(|editor, _, _| {
13329        let popover = editor.signature_help_state.popover().cloned().unwrap();
13330        assert_eq!(popover.signatures.len(), 3);
13331        // active_signature was 1, so that should be the current
13332        assert_eq!(popover.current_signature, 1);
13333        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13334        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13335        assert_eq!(
13336            popover.signatures[2].label,
13337            "fn overloaded(x: i32, y: i32, z: i32)"
13338        );
13339    });
13340
13341    // Test navigation functionality
13342    cx.update_editor(|editor, window, cx| {
13343        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13344    });
13345
13346    cx.editor(|editor, _, _| {
13347        let popover = editor.signature_help_state.popover().cloned().unwrap();
13348        assert_eq!(popover.current_signature, 2);
13349    });
13350
13351    // Test wrap around
13352    cx.update_editor(|editor, window, cx| {
13353        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13354    });
13355
13356    cx.editor(|editor, _, _| {
13357        let popover = editor.signature_help_state.popover().cloned().unwrap();
13358        assert_eq!(popover.current_signature, 0);
13359    });
13360
13361    // Test previous navigation
13362    cx.update_editor(|editor, window, cx| {
13363        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13364    });
13365
13366    cx.editor(|editor, _, _| {
13367        let popover = editor.signature_help_state.popover().cloned().unwrap();
13368        assert_eq!(popover.current_signature, 2);
13369    });
13370}
13371
13372#[gpui::test]
13373async fn test_completion_mode(cx: &mut TestAppContext) {
13374    init_test(cx, |_| {});
13375    let mut cx = EditorLspTestContext::new_rust(
13376        lsp::ServerCapabilities {
13377            completion_provider: Some(lsp::CompletionOptions {
13378                resolve_provider: Some(true),
13379                ..Default::default()
13380            }),
13381            ..Default::default()
13382        },
13383        cx,
13384    )
13385    .await;
13386
13387    struct Run {
13388        run_description: &'static str,
13389        initial_state: String,
13390        buffer_marked_text: String,
13391        completion_label: &'static str,
13392        completion_text: &'static str,
13393        expected_with_insert_mode: String,
13394        expected_with_replace_mode: String,
13395        expected_with_replace_subsequence_mode: String,
13396        expected_with_replace_suffix_mode: String,
13397    }
13398
13399    let runs = [
13400        Run {
13401            run_description: "Start of word matches completion text",
13402            initial_state: "before ediˇ after".into(),
13403            buffer_marked_text: "before <edi|> after".into(),
13404            completion_label: "editor",
13405            completion_text: "editor",
13406            expected_with_insert_mode: "before editorˇ after".into(),
13407            expected_with_replace_mode: "before editorˇ after".into(),
13408            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13409            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13410        },
13411        Run {
13412            run_description: "Accept same text at the middle of the word",
13413            initial_state: "before ediˇtor after".into(),
13414            buffer_marked_text: "before <edi|tor> after".into(),
13415            completion_label: "editor",
13416            completion_text: "editor",
13417            expected_with_insert_mode: "before editorˇtor after".into(),
13418            expected_with_replace_mode: "before editorˇ after".into(),
13419            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13420            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13421        },
13422        Run {
13423            run_description: "End of word matches completion text -- cursor at end",
13424            initial_state: "before torˇ after".into(),
13425            buffer_marked_text: "before <tor|> after".into(),
13426            completion_label: "editor",
13427            completion_text: "editor",
13428            expected_with_insert_mode: "before editorˇ after".into(),
13429            expected_with_replace_mode: "before editorˇ after".into(),
13430            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13431            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13432        },
13433        Run {
13434            run_description: "End of word matches completion text -- cursor at start",
13435            initial_state: "before ˇtor after".into(),
13436            buffer_marked_text: "before <|tor> after".into(),
13437            completion_label: "editor",
13438            completion_text: "editor",
13439            expected_with_insert_mode: "before editorˇtor after".into(),
13440            expected_with_replace_mode: "before editorˇ after".into(),
13441            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13442            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13443        },
13444        Run {
13445            run_description: "Prepend text containing whitespace",
13446            initial_state: "pˇfield: bool".into(),
13447            buffer_marked_text: "<p|field>: bool".into(),
13448            completion_label: "pub ",
13449            completion_text: "pub ",
13450            expected_with_insert_mode: "pub ˇfield: bool".into(),
13451            expected_with_replace_mode: "pub ˇ: bool".into(),
13452            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13453            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13454        },
13455        Run {
13456            run_description: "Add element to start of list",
13457            initial_state: "[element_ˇelement_2]".into(),
13458            buffer_marked_text: "[<element_|element_2>]".into(),
13459            completion_label: "element_1",
13460            completion_text: "element_1",
13461            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13462            expected_with_replace_mode: "[element_1ˇ]".into(),
13463            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13464            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13465        },
13466        Run {
13467            run_description: "Add element to start of list -- first and second elements are equal",
13468            initial_state: "[elˇelement]".into(),
13469            buffer_marked_text: "[<el|element>]".into(),
13470            completion_label: "element",
13471            completion_text: "element",
13472            expected_with_insert_mode: "[elementˇelement]".into(),
13473            expected_with_replace_mode: "[elementˇ]".into(),
13474            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13475            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13476        },
13477        Run {
13478            run_description: "Ends with matching suffix",
13479            initial_state: "SubˇError".into(),
13480            buffer_marked_text: "<Sub|Error>".into(),
13481            completion_label: "SubscriptionError",
13482            completion_text: "SubscriptionError",
13483            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13484            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13485            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13486            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13487        },
13488        Run {
13489            run_description: "Suffix is a subsequence -- contiguous",
13490            initial_state: "SubˇErr".into(),
13491            buffer_marked_text: "<Sub|Err>".into(),
13492            completion_label: "SubscriptionError",
13493            completion_text: "SubscriptionError",
13494            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13495            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13496            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13497            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13498        },
13499        Run {
13500            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13501            initial_state: "Suˇscrirr".into(),
13502            buffer_marked_text: "<Su|scrirr>".into(),
13503            completion_label: "SubscriptionError",
13504            completion_text: "SubscriptionError",
13505            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13506            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13507            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13508            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13509        },
13510        Run {
13511            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13512            initial_state: "foo(indˇix)".into(),
13513            buffer_marked_text: "foo(<ind|ix>)".into(),
13514            completion_label: "node_index",
13515            completion_text: "node_index",
13516            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13517            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13518            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13519            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13520        },
13521        Run {
13522            run_description: "Replace range ends before cursor - should extend to cursor",
13523            initial_state: "before editˇo after".into(),
13524            buffer_marked_text: "before <{ed}>it|o after".into(),
13525            completion_label: "editor",
13526            completion_text: "editor",
13527            expected_with_insert_mode: "before editorˇo after".into(),
13528            expected_with_replace_mode: "before editorˇo after".into(),
13529            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13530            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13531        },
13532        Run {
13533            run_description: "Uses label for suffix matching",
13534            initial_state: "before ediˇtor after".into(),
13535            buffer_marked_text: "before <edi|tor> after".into(),
13536            completion_label: "editor",
13537            completion_text: "editor()",
13538            expected_with_insert_mode: "before editor()ˇtor after".into(),
13539            expected_with_replace_mode: "before editor()ˇ after".into(),
13540            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13541            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13542        },
13543        Run {
13544            run_description: "Case insensitive subsequence and suffix matching",
13545            initial_state: "before EDiˇtoR after".into(),
13546            buffer_marked_text: "before <EDi|toR> after".into(),
13547            completion_label: "editor",
13548            completion_text: "editor",
13549            expected_with_insert_mode: "before editorˇtoR after".into(),
13550            expected_with_replace_mode: "before editorˇ after".into(),
13551            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13552            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13553        },
13554    ];
13555
13556    for run in runs {
13557        let run_variations = [
13558            (LspInsertMode::Insert, run.expected_with_insert_mode),
13559            (LspInsertMode::Replace, run.expected_with_replace_mode),
13560            (
13561                LspInsertMode::ReplaceSubsequence,
13562                run.expected_with_replace_subsequence_mode,
13563            ),
13564            (
13565                LspInsertMode::ReplaceSuffix,
13566                run.expected_with_replace_suffix_mode,
13567            ),
13568        ];
13569
13570        for (lsp_insert_mode, expected_text) in run_variations {
13571            eprintln!(
13572                "run = {:?}, mode = {lsp_insert_mode:.?}",
13573                run.run_description,
13574            );
13575
13576            update_test_language_settings(&mut cx, |settings| {
13577                settings.defaults.completions = Some(CompletionSettingsContent {
13578                    lsp_insert_mode: Some(lsp_insert_mode),
13579                    words: Some(WordsCompletionMode::Disabled),
13580                    words_min_length: Some(0),
13581                    ..Default::default()
13582                });
13583            });
13584
13585            cx.set_state(&run.initial_state);
13586            cx.update_editor(|editor, window, cx| {
13587                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13588            });
13589
13590            let counter = Arc::new(AtomicUsize::new(0));
13591            handle_completion_request_with_insert_and_replace(
13592                &mut cx,
13593                &run.buffer_marked_text,
13594                vec![(run.completion_label, run.completion_text)],
13595                counter.clone(),
13596            )
13597            .await;
13598            cx.condition(|editor, _| editor.context_menu_visible())
13599                .await;
13600            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13601
13602            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13603                editor
13604                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13605                    .unwrap()
13606            });
13607            cx.assert_editor_state(&expected_text);
13608            handle_resolve_completion_request(&mut cx, None).await;
13609            apply_additional_edits.await.unwrap();
13610        }
13611    }
13612}
13613
13614#[gpui::test]
13615async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13616    init_test(cx, |_| {});
13617    let mut cx = EditorLspTestContext::new_rust(
13618        lsp::ServerCapabilities {
13619            completion_provider: Some(lsp::CompletionOptions {
13620                resolve_provider: Some(true),
13621                ..Default::default()
13622            }),
13623            ..Default::default()
13624        },
13625        cx,
13626    )
13627    .await;
13628
13629    let initial_state = "SubˇError";
13630    let buffer_marked_text = "<Sub|Error>";
13631    let completion_text = "SubscriptionError";
13632    let expected_with_insert_mode = "SubscriptionErrorˇError";
13633    let expected_with_replace_mode = "SubscriptionErrorˇ";
13634
13635    update_test_language_settings(&mut cx, |settings| {
13636        settings.defaults.completions = Some(CompletionSettingsContent {
13637            words: Some(WordsCompletionMode::Disabled),
13638            words_min_length: Some(0),
13639            // set the opposite here to ensure that the action is overriding the default behavior
13640            lsp_insert_mode: Some(LspInsertMode::Insert),
13641            ..Default::default()
13642        });
13643    });
13644
13645    cx.set_state(initial_state);
13646    cx.update_editor(|editor, window, cx| {
13647        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13648    });
13649
13650    let counter = Arc::new(AtomicUsize::new(0));
13651    handle_completion_request_with_insert_and_replace(
13652        &mut cx,
13653        buffer_marked_text,
13654        vec![(completion_text, completion_text)],
13655        counter.clone(),
13656    )
13657    .await;
13658    cx.condition(|editor, _| editor.context_menu_visible())
13659        .await;
13660    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13661
13662    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13663        editor
13664            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13665            .unwrap()
13666    });
13667    cx.assert_editor_state(expected_with_replace_mode);
13668    handle_resolve_completion_request(&mut cx, None).await;
13669    apply_additional_edits.await.unwrap();
13670
13671    update_test_language_settings(&mut cx, |settings| {
13672        settings.defaults.completions = Some(CompletionSettingsContent {
13673            words: Some(WordsCompletionMode::Disabled),
13674            words_min_length: Some(0),
13675            // set the opposite here to ensure that the action is overriding the default behavior
13676            lsp_insert_mode: Some(LspInsertMode::Replace),
13677            ..Default::default()
13678        });
13679    });
13680
13681    cx.set_state(initial_state);
13682    cx.update_editor(|editor, window, cx| {
13683        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13684    });
13685    handle_completion_request_with_insert_and_replace(
13686        &mut cx,
13687        buffer_marked_text,
13688        vec![(completion_text, completion_text)],
13689        counter.clone(),
13690    )
13691    .await;
13692    cx.condition(|editor, _| editor.context_menu_visible())
13693        .await;
13694    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13695
13696    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13697        editor
13698            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13699            .unwrap()
13700    });
13701    cx.assert_editor_state(expected_with_insert_mode);
13702    handle_resolve_completion_request(&mut cx, None).await;
13703    apply_additional_edits.await.unwrap();
13704}
13705
13706#[gpui::test]
13707async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13708    init_test(cx, |_| {});
13709    let mut cx = EditorLspTestContext::new_rust(
13710        lsp::ServerCapabilities {
13711            completion_provider: Some(lsp::CompletionOptions {
13712                resolve_provider: Some(true),
13713                ..Default::default()
13714            }),
13715            ..Default::default()
13716        },
13717        cx,
13718    )
13719    .await;
13720
13721    // scenario: surrounding text matches completion text
13722    let completion_text = "to_offset";
13723    let initial_state = indoc! {"
13724        1. buf.to_offˇsuffix
13725        2. buf.to_offˇsuf
13726        3. buf.to_offˇfix
13727        4. buf.to_offˇ
13728        5. into_offˇensive
13729        6. ˇsuffix
13730        7. let ˇ //
13731        8. aaˇzz
13732        9. buf.to_off«zzzzzˇ»suffix
13733        10. buf.«ˇzzzzz»suffix
13734        11. to_off«ˇzzzzz»
13735
13736        buf.to_offˇsuffix  // newest cursor
13737    "};
13738    let completion_marked_buffer = indoc! {"
13739        1. buf.to_offsuffix
13740        2. buf.to_offsuf
13741        3. buf.to_offfix
13742        4. buf.to_off
13743        5. into_offensive
13744        6. suffix
13745        7. let  //
13746        8. aazz
13747        9. buf.to_offzzzzzsuffix
13748        10. buf.zzzzzsuffix
13749        11. to_offzzzzz
13750
13751        buf.<to_off|suffix>  // newest cursor
13752    "};
13753    let expected = indoc! {"
13754        1. buf.to_offsetˇ
13755        2. buf.to_offsetˇsuf
13756        3. buf.to_offsetˇfix
13757        4. buf.to_offsetˇ
13758        5. into_offsetˇensive
13759        6. to_offsetˇsuffix
13760        7. let to_offsetˇ //
13761        8. aato_offsetˇzz
13762        9. buf.to_offsetˇ
13763        10. buf.to_offsetˇsuffix
13764        11. to_offsetˇ
13765
13766        buf.to_offsetˇ  // newest cursor
13767    "};
13768    cx.set_state(initial_state);
13769    cx.update_editor(|editor, window, cx| {
13770        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13771    });
13772    handle_completion_request_with_insert_and_replace(
13773        &mut cx,
13774        completion_marked_buffer,
13775        vec![(completion_text, completion_text)],
13776        Arc::new(AtomicUsize::new(0)),
13777    )
13778    .await;
13779    cx.condition(|editor, _| editor.context_menu_visible())
13780        .await;
13781    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13782        editor
13783            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13784            .unwrap()
13785    });
13786    cx.assert_editor_state(expected);
13787    handle_resolve_completion_request(&mut cx, None).await;
13788    apply_additional_edits.await.unwrap();
13789
13790    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13791    let completion_text = "foo_and_bar";
13792    let initial_state = indoc! {"
13793        1. ooanbˇ
13794        2. zooanbˇ
13795        3. ooanbˇz
13796        4. zooanbˇz
13797        5. ooanˇ
13798        6. oanbˇ
13799
13800        ooanbˇ
13801    "};
13802    let completion_marked_buffer = indoc! {"
13803        1. ooanb
13804        2. zooanb
13805        3. ooanbz
13806        4. zooanbz
13807        5. ooan
13808        6. oanb
13809
13810        <ooanb|>
13811    "};
13812    let expected = indoc! {"
13813        1. foo_and_barˇ
13814        2. zfoo_and_barˇ
13815        3. foo_and_barˇz
13816        4. zfoo_and_barˇz
13817        5. ooanfoo_and_barˇ
13818        6. oanbfoo_and_barˇ
13819
13820        foo_and_barˇ
13821    "};
13822    cx.set_state(initial_state);
13823    cx.update_editor(|editor, window, cx| {
13824        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13825    });
13826    handle_completion_request_with_insert_and_replace(
13827        &mut cx,
13828        completion_marked_buffer,
13829        vec![(completion_text, completion_text)],
13830        Arc::new(AtomicUsize::new(0)),
13831    )
13832    .await;
13833    cx.condition(|editor, _| editor.context_menu_visible())
13834        .await;
13835    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13836        editor
13837            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13838            .unwrap()
13839    });
13840    cx.assert_editor_state(expected);
13841    handle_resolve_completion_request(&mut cx, None).await;
13842    apply_additional_edits.await.unwrap();
13843
13844    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13845    // (expects the same as if it was inserted at the end)
13846    let completion_text = "foo_and_bar";
13847    let initial_state = indoc! {"
13848        1. ooˇanb
13849        2. zooˇanb
13850        3. ooˇanbz
13851        4. zooˇanbz
13852
13853        ooˇanb
13854    "};
13855    let completion_marked_buffer = indoc! {"
13856        1. ooanb
13857        2. zooanb
13858        3. ooanbz
13859        4. zooanbz
13860
13861        <oo|anb>
13862    "};
13863    let expected = indoc! {"
13864        1. foo_and_barˇ
13865        2. zfoo_and_barˇ
13866        3. foo_and_barˇz
13867        4. zfoo_and_barˇz
13868
13869        foo_and_barˇ
13870    "};
13871    cx.set_state(initial_state);
13872    cx.update_editor(|editor, window, cx| {
13873        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13874    });
13875    handle_completion_request_with_insert_and_replace(
13876        &mut cx,
13877        completion_marked_buffer,
13878        vec![(completion_text, completion_text)],
13879        Arc::new(AtomicUsize::new(0)),
13880    )
13881    .await;
13882    cx.condition(|editor, _| editor.context_menu_visible())
13883        .await;
13884    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13885        editor
13886            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13887            .unwrap()
13888    });
13889    cx.assert_editor_state(expected);
13890    handle_resolve_completion_request(&mut cx, None).await;
13891    apply_additional_edits.await.unwrap();
13892}
13893
13894// This used to crash
13895#[gpui::test]
13896async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13897    init_test(cx, |_| {});
13898
13899    let buffer_text = indoc! {"
13900        fn main() {
13901            10.satu;
13902
13903            //
13904            // separate cursors so they open in different excerpts (manually reproducible)
13905            //
13906
13907            10.satu20;
13908        }
13909    "};
13910    let multibuffer_text_with_selections = indoc! {"
13911        fn main() {
13912            10.satuˇ;
13913
13914            //
13915
13916            //
13917
13918            10.satuˇ20;
13919        }
13920    "};
13921    let expected_multibuffer = indoc! {"
13922        fn main() {
13923            10.saturating_sub()ˇ;
13924
13925            //
13926
13927            //
13928
13929            10.saturating_sub()ˇ;
13930        }
13931    "};
13932
13933    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13934    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13935
13936    let fs = FakeFs::new(cx.executor());
13937    fs.insert_tree(
13938        path!("/a"),
13939        json!({
13940            "main.rs": buffer_text,
13941        }),
13942    )
13943    .await;
13944
13945    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13946    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13947    language_registry.add(rust_lang());
13948    let mut fake_servers = language_registry.register_fake_lsp(
13949        "Rust",
13950        FakeLspAdapter {
13951            capabilities: lsp::ServerCapabilities {
13952                completion_provider: Some(lsp::CompletionOptions {
13953                    resolve_provider: None,
13954                    ..lsp::CompletionOptions::default()
13955                }),
13956                ..lsp::ServerCapabilities::default()
13957            },
13958            ..FakeLspAdapter::default()
13959        },
13960    );
13961    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13962    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13963    let buffer = project
13964        .update(cx, |project, cx| {
13965            project.open_local_buffer(path!("/a/main.rs"), cx)
13966        })
13967        .await
13968        .unwrap();
13969
13970    let multi_buffer = cx.new(|cx| {
13971        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13972        multi_buffer.push_excerpts(
13973            buffer.clone(),
13974            [ExcerptRange::new(0..first_excerpt_end)],
13975            cx,
13976        );
13977        multi_buffer.push_excerpts(
13978            buffer.clone(),
13979            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13980            cx,
13981        );
13982        multi_buffer
13983    });
13984
13985    let editor = workspace
13986        .update(cx, |_, window, cx| {
13987            cx.new(|cx| {
13988                Editor::new(
13989                    EditorMode::Full {
13990                        scale_ui_elements_with_buffer_font_size: false,
13991                        show_active_line_background: false,
13992                        sized_by_content: false,
13993                    },
13994                    multi_buffer.clone(),
13995                    Some(project.clone()),
13996                    window,
13997                    cx,
13998                )
13999            })
14000        })
14001        .unwrap();
14002
14003    let pane = workspace
14004        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14005        .unwrap();
14006    pane.update_in(cx, |pane, window, cx| {
14007        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14008    });
14009
14010    let fake_server = fake_servers.next().await.unwrap();
14011
14012    editor.update_in(cx, |editor, window, cx| {
14013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14014            s.select_ranges([
14015                Point::new(1, 11)..Point::new(1, 11),
14016                Point::new(7, 11)..Point::new(7, 11),
14017            ])
14018        });
14019
14020        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14021    });
14022
14023    editor.update_in(cx, |editor, window, cx| {
14024        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14025    });
14026
14027    fake_server
14028        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14029            let completion_item = lsp::CompletionItem {
14030                label: "saturating_sub()".into(),
14031                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14032                    lsp::InsertReplaceEdit {
14033                        new_text: "saturating_sub()".to_owned(),
14034                        insert: lsp::Range::new(
14035                            lsp::Position::new(7, 7),
14036                            lsp::Position::new(7, 11),
14037                        ),
14038                        replace: lsp::Range::new(
14039                            lsp::Position::new(7, 7),
14040                            lsp::Position::new(7, 13),
14041                        ),
14042                    },
14043                )),
14044                ..lsp::CompletionItem::default()
14045            };
14046
14047            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14048        })
14049        .next()
14050        .await
14051        .unwrap();
14052
14053    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14054        .await;
14055
14056    editor
14057        .update_in(cx, |editor, window, cx| {
14058            editor
14059                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14060                .unwrap()
14061        })
14062        .await
14063        .unwrap();
14064
14065    editor.update(cx, |editor, cx| {
14066        assert_text_with_selections(editor, expected_multibuffer, cx);
14067    })
14068}
14069
14070#[gpui::test]
14071async fn test_completion(cx: &mut TestAppContext) {
14072    init_test(cx, |_| {});
14073
14074    let mut cx = EditorLspTestContext::new_rust(
14075        lsp::ServerCapabilities {
14076            completion_provider: Some(lsp::CompletionOptions {
14077                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14078                resolve_provider: Some(true),
14079                ..Default::default()
14080            }),
14081            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14082            ..Default::default()
14083        },
14084        cx,
14085    )
14086    .await;
14087    let counter = Arc::new(AtomicUsize::new(0));
14088
14089    cx.set_state(indoc! {"
14090        oneˇ
14091        two
14092        three
14093    "});
14094    cx.simulate_keystroke(".");
14095    handle_completion_request(
14096        indoc! {"
14097            one.|<>
14098            two
14099            three
14100        "},
14101        vec!["first_completion", "second_completion"],
14102        true,
14103        counter.clone(),
14104        &mut cx,
14105    )
14106    .await;
14107    cx.condition(|editor, _| editor.context_menu_visible())
14108        .await;
14109    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14110
14111    let _handler = handle_signature_help_request(
14112        &mut cx,
14113        lsp::SignatureHelp {
14114            signatures: vec![lsp::SignatureInformation {
14115                label: "test signature".to_string(),
14116                documentation: None,
14117                parameters: Some(vec![lsp::ParameterInformation {
14118                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14119                    documentation: None,
14120                }]),
14121                active_parameter: None,
14122            }],
14123            active_signature: None,
14124            active_parameter: None,
14125        },
14126    );
14127    cx.update_editor(|editor, window, cx| {
14128        assert!(
14129            !editor.signature_help_state.is_shown(),
14130            "No signature help was called for"
14131        );
14132        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14133    });
14134    cx.run_until_parked();
14135    cx.update_editor(|editor, _, _| {
14136        assert!(
14137            !editor.signature_help_state.is_shown(),
14138            "No signature help should be shown when completions menu is open"
14139        );
14140    });
14141
14142    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14143        editor.context_menu_next(&Default::default(), window, cx);
14144        editor
14145            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14146            .unwrap()
14147    });
14148    cx.assert_editor_state(indoc! {"
14149        one.second_completionˇ
14150        two
14151        three
14152    "});
14153
14154    handle_resolve_completion_request(
14155        &mut cx,
14156        Some(vec![
14157            (
14158                //This overlaps with the primary completion edit which is
14159                //misbehavior from the LSP spec, test that we filter it out
14160                indoc! {"
14161                    one.second_ˇcompletion
14162                    two
14163                    threeˇ
14164                "},
14165                "overlapping additional edit",
14166            ),
14167            (
14168                indoc! {"
14169                    one.second_completion
14170                    two
14171                    threeˇ
14172                "},
14173                "\nadditional edit",
14174            ),
14175        ]),
14176    )
14177    .await;
14178    apply_additional_edits.await.unwrap();
14179    cx.assert_editor_state(indoc! {"
14180        one.second_completionˇ
14181        two
14182        three
14183        additional edit
14184    "});
14185
14186    cx.set_state(indoc! {"
14187        one.second_completion
14188        twoˇ
14189        threeˇ
14190        additional edit
14191    "});
14192    cx.simulate_keystroke(" ");
14193    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14194    cx.simulate_keystroke("s");
14195    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14196
14197    cx.assert_editor_state(indoc! {"
14198        one.second_completion
14199        two sˇ
14200        three sˇ
14201        additional edit
14202    "});
14203    handle_completion_request(
14204        indoc! {"
14205            one.second_completion
14206            two s
14207            three <s|>
14208            additional edit
14209        "},
14210        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14211        true,
14212        counter.clone(),
14213        &mut cx,
14214    )
14215    .await;
14216    cx.condition(|editor, _| editor.context_menu_visible())
14217        .await;
14218    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14219
14220    cx.simulate_keystroke("i");
14221
14222    handle_completion_request(
14223        indoc! {"
14224            one.second_completion
14225            two si
14226            three <si|>
14227            additional edit
14228        "},
14229        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14230        true,
14231        counter.clone(),
14232        &mut cx,
14233    )
14234    .await;
14235    cx.condition(|editor, _| editor.context_menu_visible())
14236        .await;
14237    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14238
14239    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14240        editor
14241            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14242            .unwrap()
14243    });
14244    cx.assert_editor_state(indoc! {"
14245        one.second_completion
14246        two sixth_completionˇ
14247        three sixth_completionˇ
14248        additional edit
14249    "});
14250
14251    apply_additional_edits.await.unwrap();
14252
14253    update_test_language_settings(&mut cx, |settings| {
14254        settings.defaults.show_completions_on_input = Some(false);
14255    });
14256    cx.set_state("editorˇ");
14257    cx.simulate_keystroke(".");
14258    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14259    cx.simulate_keystrokes("c l o");
14260    cx.assert_editor_state("editor.cloˇ");
14261    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14262    cx.update_editor(|editor, window, cx| {
14263        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14264    });
14265    handle_completion_request(
14266        "editor.<clo|>",
14267        vec!["close", "clobber"],
14268        true,
14269        counter.clone(),
14270        &mut cx,
14271    )
14272    .await;
14273    cx.condition(|editor, _| editor.context_menu_visible())
14274        .await;
14275    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14276
14277    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14278        editor
14279            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14280            .unwrap()
14281    });
14282    cx.assert_editor_state("editor.clobberˇ");
14283    handle_resolve_completion_request(&mut cx, None).await;
14284    apply_additional_edits.await.unwrap();
14285}
14286
14287#[gpui::test]
14288async fn test_completion_reuse(cx: &mut TestAppContext) {
14289    init_test(cx, |_| {});
14290
14291    let mut cx = EditorLspTestContext::new_rust(
14292        lsp::ServerCapabilities {
14293            completion_provider: Some(lsp::CompletionOptions {
14294                trigger_characters: Some(vec![".".to_string()]),
14295                ..Default::default()
14296            }),
14297            ..Default::default()
14298        },
14299        cx,
14300    )
14301    .await;
14302
14303    let counter = Arc::new(AtomicUsize::new(0));
14304    cx.set_state("objˇ");
14305    cx.simulate_keystroke(".");
14306
14307    // Initial completion request returns complete results
14308    let is_incomplete = false;
14309    handle_completion_request(
14310        "obj.|<>",
14311        vec!["a", "ab", "abc"],
14312        is_incomplete,
14313        counter.clone(),
14314        &mut cx,
14315    )
14316    .await;
14317    cx.run_until_parked();
14318    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14319    cx.assert_editor_state("obj.ˇ");
14320    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14321
14322    // Type "a" - filters existing completions
14323    cx.simulate_keystroke("a");
14324    cx.run_until_parked();
14325    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14326    cx.assert_editor_state("obj.aˇ");
14327    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14328
14329    // Type "b" - filters existing completions
14330    cx.simulate_keystroke("b");
14331    cx.run_until_parked();
14332    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14333    cx.assert_editor_state("obj.abˇ");
14334    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14335
14336    // Type "c" - filters existing completions
14337    cx.simulate_keystroke("c");
14338    cx.run_until_parked();
14339    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14340    cx.assert_editor_state("obj.abcˇ");
14341    check_displayed_completions(vec!["abc"], &mut cx);
14342
14343    // Backspace to delete "c" - filters existing completions
14344    cx.update_editor(|editor, window, cx| {
14345        editor.backspace(&Backspace, window, cx);
14346    });
14347    cx.run_until_parked();
14348    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14349    cx.assert_editor_state("obj.abˇ");
14350    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14351
14352    // Moving cursor to the left dismisses menu.
14353    cx.update_editor(|editor, window, cx| {
14354        editor.move_left(&MoveLeft, window, cx);
14355    });
14356    cx.run_until_parked();
14357    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14358    cx.assert_editor_state("obj.aˇb");
14359    cx.update_editor(|editor, _, _| {
14360        assert_eq!(editor.context_menu_visible(), false);
14361    });
14362
14363    // Type "b" - new request
14364    cx.simulate_keystroke("b");
14365    let is_incomplete = false;
14366    handle_completion_request(
14367        "obj.<ab|>a",
14368        vec!["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), 2);
14376    cx.assert_editor_state("obj.abˇb");
14377    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14378
14379    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14380    cx.update_editor(|editor, window, cx| {
14381        editor.backspace(&Backspace, window, cx);
14382    });
14383    let is_incomplete = false;
14384    handle_completion_request(
14385        "obj.<a|>b",
14386        vec!["a", "ab", "abc"],
14387        is_incomplete,
14388        counter.clone(),
14389        &mut cx,
14390    )
14391    .await;
14392    cx.run_until_parked();
14393    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14394    cx.assert_editor_state("obj.aˇb");
14395    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14396
14397    // Backspace to delete "a" - dismisses menu.
14398    cx.update_editor(|editor, window, cx| {
14399        editor.backspace(&Backspace, window, cx);
14400    });
14401    cx.run_until_parked();
14402    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14403    cx.assert_editor_state("obj.ˇb");
14404    cx.update_editor(|editor, _, _| {
14405        assert_eq!(editor.context_menu_visible(), false);
14406    });
14407}
14408
14409#[gpui::test]
14410async fn test_word_completion(cx: &mut TestAppContext) {
14411    let lsp_fetch_timeout_ms = 10;
14412    init_test(cx, |language_settings| {
14413        language_settings.defaults.completions = Some(CompletionSettingsContent {
14414            words_min_length: Some(0),
14415            lsp_fetch_timeout_ms: Some(10),
14416            lsp_insert_mode: Some(LspInsertMode::Insert),
14417            ..Default::default()
14418        });
14419    });
14420
14421    let mut cx = EditorLspTestContext::new_rust(
14422        lsp::ServerCapabilities {
14423            completion_provider: Some(lsp::CompletionOptions {
14424                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14425                ..lsp::CompletionOptions::default()
14426            }),
14427            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14428            ..lsp::ServerCapabilities::default()
14429        },
14430        cx,
14431    )
14432    .await;
14433
14434    let throttle_completions = Arc::new(AtomicBool::new(false));
14435
14436    let lsp_throttle_completions = throttle_completions.clone();
14437    let _completion_requests_handler =
14438        cx.lsp
14439            .server
14440            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14441                let lsp_throttle_completions = lsp_throttle_completions.clone();
14442                let cx = cx.clone();
14443                async move {
14444                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14445                        cx.background_executor()
14446                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14447                            .await;
14448                    }
14449                    Ok(Some(lsp::CompletionResponse::Array(vec![
14450                        lsp::CompletionItem {
14451                            label: "first".into(),
14452                            ..lsp::CompletionItem::default()
14453                        },
14454                        lsp::CompletionItem {
14455                            label: "last".into(),
14456                            ..lsp::CompletionItem::default()
14457                        },
14458                    ])))
14459                }
14460            });
14461
14462    cx.set_state(indoc! {"
14463        oneˇ
14464        two
14465        three
14466    "});
14467    cx.simulate_keystroke(".");
14468    cx.executor().run_until_parked();
14469    cx.condition(|editor, _| editor.context_menu_visible())
14470        .await;
14471    cx.update_editor(|editor, window, cx| {
14472        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14473        {
14474            assert_eq!(
14475                completion_menu_entries(menu),
14476                &["first", "last"],
14477                "When LSP server is fast to reply, no fallback word completions are used"
14478            );
14479        } else {
14480            panic!("expected completion menu to be open");
14481        }
14482        editor.cancel(&Cancel, window, cx);
14483    });
14484    cx.executor().run_until_parked();
14485    cx.condition(|editor, _| !editor.context_menu_visible())
14486        .await;
14487
14488    throttle_completions.store(true, atomic::Ordering::Release);
14489    cx.simulate_keystroke(".");
14490    cx.executor()
14491        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14492    cx.executor().run_until_parked();
14493    cx.condition(|editor, _| editor.context_menu_visible())
14494        .await;
14495    cx.update_editor(|editor, _, _| {
14496        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14497        {
14498            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14499                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14500        } else {
14501            panic!("expected completion menu to be open");
14502        }
14503    });
14504}
14505
14506#[gpui::test]
14507async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14508    init_test(cx, |language_settings| {
14509        language_settings.defaults.completions = Some(CompletionSettingsContent {
14510            words: Some(WordsCompletionMode::Enabled),
14511            words_min_length: Some(0),
14512            lsp_insert_mode: Some(LspInsertMode::Insert),
14513            ..Default::default()
14514        });
14515    });
14516
14517    let mut cx = EditorLspTestContext::new_rust(
14518        lsp::ServerCapabilities {
14519            completion_provider: Some(lsp::CompletionOptions {
14520                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14521                ..lsp::CompletionOptions::default()
14522            }),
14523            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14524            ..lsp::ServerCapabilities::default()
14525        },
14526        cx,
14527    )
14528    .await;
14529
14530    let _completion_requests_handler =
14531        cx.lsp
14532            .server
14533            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14534                Ok(Some(lsp::CompletionResponse::Array(vec![
14535                    lsp::CompletionItem {
14536                        label: "first".into(),
14537                        ..lsp::CompletionItem::default()
14538                    },
14539                    lsp::CompletionItem {
14540                        label: "last".into(),
14541                        ..lsp::CompletionItem::default()
14542                    },
14543                ])))
14544            });
14545
14546    cx.set_state(indoc! {"ˇ
14547        first
14548        last
14549        second
14550    "});
14551    cx.simulate_keystroke(".");
14552    cx.executor().run_until_parked();
14553    cx.condition(|editor, _| editor.context_menu_visible())
14554        .await;
14555    cx.update_editor(|editor, _, _| {
14556        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14557        {
14558            assert_eq!(
14559                completion_menu_entries(menu),
14560                &["first", "last", "second"],
14561                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14562            );
14563        } else {
14564            panic!("expected completion menu to be open");
14565        }
14566    });
14567}
14568
14569#[gpui::test]
14570async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14571    init_test(cx, |language_settings| {
14572        language_settings.defaults.completions = Some(CompletionSettingsContent {
14573            words: Some(WordsCompletionMode::Disabled),
14574            words_min_length: Some(0),
14575            lsp_insert_mode: Some(LspInsertMode::Insert),
14576            ..Default::default()
14577        });
14578    });
14579
14580    let mut cx = EditorLspTestContext::new_rust(
14581        lsp::ServerCapabilities {
14582            completion_provider: Some(lsp::CompletionOptions {
14583                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14584                ..lsp::CompletionOptions::default()
14585            }),
14586            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14587            ..lsp::ServerCapabilities::default()
14588        },
14589        cx,
14590    )
14591    .await;
14592
14593    let _completion_requests_handler =
14594        cx.lsp
14595            .server
14596            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14597                panic!("LSP completions should not be queried when dealing with word completions")
14598            });
14599
14600    cx.set_state(indoc! {"ˇ
14601        first
14602        last
14603        second
14604    "});
14605    cx.update_editor(|editor, window, cx| {
14606        editor.show_word_completions(&ShowWordCompletions, window, cx);
14607    });
14608    cx.executor().run_until_parked();
14609    cx.condition(|editor, _| editor.context_menu_visible())
14610        .await;
14611    cx.update_editor(|editor, _, _| {
14612        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14613        {
14614            assert_eq!(
14615                completion_menu_entries(menu),
14616                &["first", "last", "second"],
14617                "`ShowWordCompletions` action should show word completions"
14618            );
14619        } else {
14620            panic!("expected completion menu to be open");
14621        }
14622    });
14623
14624    cx.simulate_keystroke("l");
14625    cx.executor().run_until_parked();
14626    cx.condition(|editor, _| editor.context_menu_visible())
14627        .await;
14628    cx.update_editor(|editor, _, _| {
14629        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14630        {
14631            assert_eq!(
14632                completion_menu_entries(menu),
14633                &["last"],
14634                "After showing word completions, further editing should filter them and not query the LSP"
14635            );
14636        } else {
14637            panic!("expected completion menu to be open");
14638        }
14639    });
14640}
14641
14642#[gpui::test]
14643async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14644    init_test(cx, |language_settings| {
14645        language_settings.defaults.completions = Some(CompletionSettingsContent {
14646            words_min_length: Some(0),
14647            lsp: Some(false),
14648            lsp_insert_mode: Some(LspInsertMode::Insert),
14649            ..Default::default()
14650        });
14651    });
14652
14653    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14654
14655    cx.set_state(indoc! {"ˇ
14656        0_usize
14657        let
14658        33
14659        4.5f32
14660    "});
14661    cx.update_editor(|editor, window, cx| {
14662        editor.show_completions(&ShowCompletions::default(), window, cx);
14663    });
14664    cx.executor().run_until_parked();
14665    cx.condition(|editor, _| editor.context_menu_visible())
14666        .await;
14667    cx.update_editor(|editor, window, cx| {
14668        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14669        {
14670            assert_eq!(
14671                completion_menu_entries(menu),
14672                &["let"],
14673                "With no digits in the completion query, no digits should be in the word completions"
14674            );
14675        } else {
14676            panic!("expected completion menu to be open");
14677        }
14678        editor.cancel(&Cancel, window, cx);
14679    });
14680
14681    cx.set_state(indoc! {"14682        0_usize
14683        let
14684        3
14685        33.35f32
14686    "});
14687    cx.update_editor(|editor, window, cx| {
14688        editor.show_completions(&ShowCompletions::default(), window, cx);
14689    });
14690    cx.executor().run_until_parked();
14691    cx.condition(|editor, _| editor.context_menu_visible())
14692        .await;
14693    cx.update_editor(|editor, _, _| {
14694        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14695        {
14696            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14697                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14698        } else {
14699            panic!("expected completion menu to be open");
14700        }
14701    });
14702}
14703
14704#[gpui::test]
14705async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14706    init_test(cx, |language_settings| {
14707        language_settings.defaults.completions = Some(CompletionSettingsContent {
14708            words: Some(WordsCompletionMode::Enabled),
14709            words_min_length: Some(3),
14710            lsp_insert_mode: Some(LspInsertMode::Insert),
14711            ..Default::default()
14712        });
14713    });
14714
14715    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14716    cx.set_state(indoc! {"ˇ
14717        wow
14718        wowen
14719        wowser
14720    "});
14721    cx.simulate_keystroke("w");
14722    cx.executor().run_until_parked();
14723    cx.update_editor(|editor, _, _| {
14724        if editor.context_menu.borrow_mut().is_some() {
14725            panic!(
14726                "expected completion menu to be hidden, as words completion threshold is not met"
14727            );
14728        }
14729    });
14730
14731    cx.update_editor(|editor, window, cx| {
14732        editor.show_word_completions(&ShowWordCompletions, window, cx);
14733    });
14734    cx.executor().run_until_parked();
14735    cx.update_editor(|editor, window, cx| {
14736        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14737        {
14738            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");
14739        } else {
14740            panic!("expected completion menu to be open after the word completions are called with an action");
14741        }
14742
14743        editor.cancel(&Cancel, window, cx);
14744    });
14745    cx.update_editor(|editor, _, _| {
14746        if editor.context_menu.borrow_mut().is_some() {
14747            panic!("expected completion menu to be hidden after canceling");
14748        }
14749    });
14750
14751    cx.simulate_keystroke("o");
14752    cx.executor().run_until_parked();
14753    cx.update_editor(|editor, _, _| {
14754        if editor.context_menu.borrow_mut().is_some() {
14755            panic!(
14756                "expected completion menu to be hidden, as words completion threshold is not met still"
14757            );
14758        }
14759    });
14760
14761    cx.simulate_keystroke("w");
14762    cx.executor().run_until_parked();
14763    cx.update_editor(|editor, _, _| {
14764        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14765        {
14766            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14767        } else {
14768            panic!("expected completion menu to be open after the word completions threshold is met");
14769        }
14770    });
14771}
14772
14773#[gpui::test]
14774async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14775    init_test(cx, |language_settings| {
14776        language_settings.defaults.completions = Some(CompletionSettingsContent {
14777            words: Some(WordsCompletionMode::Enabled),
14778            words_min_length: Some(0),
14779            lsp_insert_mode: Some(LspInsertMode::Insert),
14780            ..Default::default()
14781        });
14782    });
14783
14784    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14785    cx.update_editor(|editor, _, _| {
14786        editor.disable_word_completions();
14787    });
14788    cx.set_state(indoc! {"ˇ
14789        wow
14790        wowen
14791        wowser
14792    "});
14793    cx.simulate_keystroke("w");
14794    cx.executor().run_until_parked();
14795    cx.update_editor(|editor, _, _| {
14796        if editor.context_menu.borrow_mut().is_some() {
14797            panic!(
14798                "expected completion menu to be hidden, as words completion are disabled for this editor"
14799            );
14800        }
14801    });
14802
14803    cx.update_editor(|editor, window, cx| {
14804        editor.show_word_completions(&ShowWordCompletions, window, cx);
14805    });
14806    cx.executor().run_until_parked();
14807    cx.update_editor(|editor, _, _| {
14808        if editor.context_menu.borrow_mut().is_some() {
14809            panic!(
14810                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14811            );
14812        }
14813    });
14814}
14815
14816fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14817    let position = || lsp::Position {
14818        line: params.text_document_position.position.line,
14819        character: params.text_document_position.position.character,
14820    };
14821    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14822        range: lsp::Range {
14823            start: position(),
14824            end: position(),
14825        },
14826        new_text: text.to_string(),
14827    }))
14828}
14829
14830#[gpui::test]
14831async fn test_multiline_completion(cx: &mut TestAppContext) {
14832    init_test(cx, |_| {});
14833
14834    let fs = FakeFs::new(cx.executor());
14835    fs.insert_tree(
14836        path!("/a"),
14837        json!({
14838            "main.ts": "a",
14839        }),
14840    )
14841    .await;
14842
14843    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14844    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14845    let typescript_language = Arc::new(Language::new(
14846        LanguageConfig {
14847            name: "TypeScript".into(),
14848            matcher: LanguageMatcher {
14849                path_suffixes: vec!["ts".to_string()],
14850                ..LanguageMatcher::default()
14851            },
14852            line_comments: vec!["// ".into()],
14853            ..LanguageConfig::default()
14854        },
14855        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14856    ));
14857    language_registry.add(typescript_language.clone());
14858    let mut fake_servers = language_registry.register_fake_lsp(
14859        "TypeScript",
14860        FakeLspAdapter {
14861            capabilities: lsp::ServerCapabilities {
14862                completion_provider: Some(lsp::CompletionOptions {
14863                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14864                    ..lsp::CompletionOptions::default()
14865                }),
14866                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14867                ..lsp::ServerCapabilities::default()
14868            },
14869            // Emulate vtsls label generation
14870            label_for_completion: Some(Box::new(|item, _| {
14871                let text = if let Some(description) = item
14872                    .label_details
14873                    .as_ref()
14874                    .and_then(|label_details| label_details.description.as_ref())
14875                {
14876                    format!("{} {}", item.label, description)
14877                } else if let Some(detail) = &item.detail {
14878                    format!("{} {}", item.label, detail)
14879                } else {
14880                    item.label.clone()
14881                };
14882                let len = text.len();
14883                Some(language::CodeLabel {
14884                    text,
14885                    runs: Vec::new(),
14886                    filter_range: 0..len,
14887                })
14888            })),
14889            ..FakeLspAdapter::default()
14890        },
14891    );
14892    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14893    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14894    let worktree_id = workspace
14895        .update(cx, |workspace, _window, cx| {
14896            workspace.project().update(cx, |project, cx| {
14897                project.worktrees(cx).next().unwrap().read(cx).id()
14898            })
14899        })
14900        .unwrap();
14901    let _buffer = project
14902        .update(cx, |project, cx| {
14903            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14904        })
14905        .await
14906        .unwrap();
14907    let editor = workspace
14908        .update(cx, |workspace, window, cx| {
14909            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14910        })
14911        .unwrap()
14912        .await
14913        .unwrap()
14914        .downcast::<Editor>()
14915        .unwrap();
14916    let fake_server = fake_servers.next().await.unwrap();
14917
14918    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14919    let multiline_label_2 = "a\nb\nc\n";
14920    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14921    let multiline_description = "d\ne\nf\n";
14922    let multiline_detail_2 = "g\nh\ni\n";
14923
14924    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14925        move |params, _| async move {
14926            Ok(Some(lsp::CompletionResponse::Array(vec![
14927                lsp::CompletionItem {
14928                    label: multiline_label.to_string(),
14929                    text_edit: gen_text_edit(&params, "new_text_1"),
14930                    ..lsp::CompletionItem::default()
14931                },
14932                lsp::CompletionItem {
14933                    label: "single line label 1".to_string(),
14934                    detail: Some(multiline_detail.to_string()),
14935                    text_edit: gen_text_edit(&params, "new_text_2"),
14936                    ..lsp::CompletionItem::default()
14937                },
14938                lsp::CompletionItem {
14939                    label: "single line label 2".to_string(),
14940                    label_details: Some(lsp::CompletionItemLabelDetails {
14941                        description: Some(multiline_description.to_string()),
14942                        detail: None,
14943                    }),
14944                    text_edit: gen_text_edit(&params, "new_text_2"),
14945                    ..lsp::CompletionItem::default()
14946                },
14947                lsp::CompletionItem {
14948                    label: multiline_label_2.to_string(),
14949                    detail: Some(multiline_detail_2.to_string()),
14950                    text_edit: gen_text_edit(&params, "new_text_3"),
14951                    ..lsp::CompletionItem::default()
14952                },
14953                lsp::CompletionItem {
14954                    label: "Label with many     spaces and \t but without newlines".to_string(),
14955                    detail: Some(
14956                        "Details with many     spaces and \t but without newlines".to_string(),
14957                    ),
14958                    text_edit: gen_text_edit(&params, "new_text_4"),
14959                    ..lsp::CompletionItem::default()
14960                },
14961            ])))
14962        },
14963    );
14964
14965    editor.update_in(cx, |editor, window, cx| {
14966        cx.focus_self(window);
14967        editor.move_to_end(&MoveToEnd, window, cx);
14968        editor.handle_input(".", window, cx);
14969    });
14970    cx.run_until_parked();
14971    completion_handle.next().await.unwrap();
14972
14973    editor.update(cx, |editor, _| {
14974        assert!(editor.context_menu_visible());
14975        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14976        {
14977            let completion_labels = menu
14978                .completions
14979                .borrow()
14980                .iter()
14981                .map(|c| c.label.text.clone())
14982                .collect::<Vec<_>>();
14983            assert_eq!(
14984                completion_labels,
14985                &[
14986                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14987                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14988                    "single line label 2 d e f ",
14989                    "a b c g h i ",
14990                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14991                ],
14992                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14993            );
14994
14995            for completion in menu
14996                .completions
14997                .borrow()
14998                .iter() {
14999                    assert_eq!(
15000                        completion.label.filter_range,
15001                        0..completion.label.text.len(),
15002                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15003                    );
15004                }
15005        } else {
15006            panic!("expected completion menu to be open");
15007        }
15008    });
15009}
15010
15011#[gpui::test]
15012async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15013    init_test(cx, |_| {});
15014    let mut cx = EditorLspTestContext::new_rust(
15015        lsp::ServerCapabilities {
15016            completion_provider: Some(lsp::CompletionOptions {
15017                trigger_characters: Some(vec![".".to_string()]),
15018                ..Default::default()
15019            }),
15020            ..Default::default()
15021        },
15022        cx,
15023    )
15024    .await;
15025    cx.lsp
15026        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15027            Ok(Some(lsp::CompletionResponse::Array(vec![
15028                lsp::CompletionItem {
15029                    label: "first".into(),
15030                    ..Default::default()
15031                },
15032                lsp::CompletionItem {
15033                    label: "last".into(),
15034                    ..Default::default()
15035                },
15036            ])))
15037        });
15038    cx.set_state("variableˇ");
15039    cx.simulate_keystroke(".");
15040    cx.executor().run_until_parked();
15041
15042    cx.update_editor(|editor, _, _| {
15043        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15044        {
15045            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15046        } else {
15047            panic!("expected completion menu to be open");
15048        }
15049    });
15050
15051    cx.update_editor(|editor, window, cx| {
15052        editor.move_page_down(&MovePageDown::default(), window, cx);
15053        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15054        {
15055            assert!(
15056                menu.selected_item == 1,
15057                "expected PageDown to select the last item from the context menu"
15058            );
15059        } else {
15060            panic!("expected completion menu to stay open after PageDown");
15061        }
15062    });
15063
15064    cx.update_editor(|editor, window, cx| {
15065        editor.move_page_up(&MovePageUp::default(), window, cx);
15066        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15067        {
15068            assert!(
15069                menu.selected_item == 0,
15070                "expected PageUp to select the first item from the context menu"
15071            );
15072        } else {
15073            panic!("expected completion menu to stay open after PageUp");
15074        }
15075    });
15076}
15077
15078#[gpui::test]
15079async fn test_as_is_completions(cx: &mut TestAppContext) {
15080    init_test(cx, |_| {});
15081    let mut cx = EditorLspTestContext::new_rust(
15082        lsp::ServerCapabilities {
15083            completion_provider: Some(lsp::CompletionOptions {
15084                ..Default::default()
15085            }),
15086            ..Default::default()
15087        },
15088        cx,
15089    )
15090    .await;
15091    cx.lsp
15092        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15093            Ok(Some(lsp::CompletionResponse::Array(vec![
15094                lsp::CompletionItem {
15095                    label: "unsafe".into(),
15096                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15097                        range: lsp::Range {
15098                            start: lsp::Position {
15099                                line: 1,
15100                                character: 2,
15101                            },
15102                            end: lsp::Position {
15103                                line: 1,
15104                                character: 3,
15105                            },
15106                        },
15107                        new_text: "unsafe".to_string(),
15108                    })),
15109                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15110                    ..Default::default()
15111                },
15112            ])))
15113        });
15114    cx.set_state("fn a() {}\n");
15115    cx.executor().run_until_parked();
15116    cx.update_editor(|editor, window, cx| {
15117        editor.show_completions(
15118            &ShowCompletions {
15119                trigger: Some("\n".into()),
15120            },
15121            window,
15122            cx,
15123        );
15124    });
15125    cx.executor().run_until_parked();
15126
15127    cx.update_editor(|editor, window, cx| {
15128        editor.confirm_completion(&Default::default(), window, cx)
15129    });
15130    cx.executor().run_until_parked();
15131    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15132}
15133
15134#[gpui::test]
15135async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15136    init_test(cx, |_| {});
15137    let language =
15138        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15139    let mut cx = EditorLspTestContext::new(
15140        language,
15141        lsp::ServerCapabilities {
15142            completion_provider: Some(lsp::CompletionOptions {
15143                ..lsp::CompletionOptions::default()
15144            }),
15145            ..lsp::ServerCapabilities::default()
15146        },
15147        cx,
15148    )
15149    .await;
15150
15151    cx.set_state(
15152        "#ifndef BAR_H
15153#define BAR_H
15154
15155#include <stdbool.h>
15156
15157int fn_branch(bool do_branch1, bool do_branch2);
15158
15159#endif // BAR_H
15160ˇ",
15161    );
15162    cx.executor().run_until_parked();
15163    cx.update_editor(|editor, window, cx| {
15164        editor.handle_input("#", window, cx);
15165    });
15166    cx.executor().run_until_parked();
15167    cx.update_editor(|editor, window, cx| {
15168        editor.handle_input("i", window, cx);
15169    });
15170    cx.executor().run_until_parked();
15171    cx.update_editor(|editor, window, cx| {
15172        editor.handle_input("n", window, cx);
15173    });
15174    cx.executor().run_until_parked();
15175    cx.assert_editor_state(
15176        "#ifndef BAR_H
15177#define BAR_H
15178
15179#include <stdbool.h>
15180
15181int fn_branch(bool do_branch1, bool do_branch2);
15182
15183#endif // BAR_H
15184#inˇ",
15185    );
15186
15187    cx.lsp
15188        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15189            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15190                is_incomplete: false,
15191                item_defaults: None,
15192                items: vec![lsp::CompletionItem {
15193                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15194                    label_details: Some(lsp::CompletionItemLabelDetails {
15195                        detail: Some("header".to_string()),
15196                        description: None,
15197                    }),
15198                    label: " include".to_string(),
15199                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15200                        range: lsp::Range {
15201                            start: lsp::Position {
15202                                line: 8,
15203                                character: 1,
15204                            },
15205                            end: lsp::Position {
15206                                line: 8,
15207                                character: 1,
15208                            },
15209                        },
15210                        new_text: "include \"$0\"".to_string(),
15211                    })),
15212                    sort_text: Some("40b67681include".to_string()),
15213                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15214                    filter_text: Some("include".to_string()),
15215                    insert_text: Some("include \"$0\"".to_string()),
15216                    ..lsp::CompletionItem::default()
15217                }],
15218            })))
15219        });
15220    cx.update_editor(|editor, window, cx| {
15221        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15222    });
15223    cx.executor().run_until_parked();
15224    cx.update_editor(|editor, window, cx| {
15225        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15226    });
15227    cx.executor().run_until_parked();
15228    cx.assert_editor_state(
15229        "#ifndef BAR_H
15230#define BAR_H
15231
15232#include <stdbool.h>
15233
15234int fn_branch(bool do_branch1, bool do_branch2);
15235
15236#endif // BAR_H
15237#include \"ˇ\"",
15238    );
15239
15240    cx.lsp
15241        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15242            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15243                is_incomplete: true,
15244                item_defaults: None,
15245                items: vec![lsp::CompletionItem {
15246                    kind: Some(lsp::CompletionItemKind::FILE),
15247                    label: "AGL/".to_string(),
15248                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15249                        range: lsp::Range {
15250                            start: lsp::Position {
15251                                line: 8,
15252                                character: 10,
15253                            },
15254                            end: lsp::Position {
15255                                line: 8,
15256                                character: 11,
15257                            },
15258                        },
15259                        new_text: "AGL/".to_string(),
15260                    })),
15261                    sort_text: Some("40b67681AGL/".to_string()),
15262                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15263                    filter_text: Some("AGL/".to_string()),
15264                    insert_text: Some("AGL/".to_string()),
15265                    ..lsp::CompletionItem::default()
15266                }],
15267            })))
15268        });
15269    cx.update_editor(|editor, window, cx| {
15270        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15271    });
15272    cx.executor().run_until_parked();
15273    cx.update_editor(|editor, window, cx| {
15274        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15275    });
15276    cx.executor().run_until_parked();
15277    cx.assert_editor_state(
15278        r##"#ifndef BAR_H
15279#define BAR_H
15280
15281#include <stdbool.h>
15282
15283int fn_branch(bool do_branch1, bool do_branch2);
15284
15285#endif // BAR_H
15286#include "AGL/ˇ"##,
15287    );
15288
15289    cx.update_editor(|editor, window, cx| {
15290        editor.handle_input("\"", window, cx);
15291    });
15292    cx.executor().run_until_parked();
15293    cx.assert_editor_state(
15294        r##"#ifndef BAR_H
15295#define BAR_H
15296
15297#include <stdbool.h>
15298
15299int fn_branch(bool do_branch1, bool do_branch2);
15300
15301#endif // BAR_H
15302#include "AGL/"ˇ"##,
15303    );
15304}
15305
15306#[gpui::test]
15307async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15308    init_test(cx, |_| {});
15309
15310    let mut cx = EditorLspTestContext::new_rust(
15311        lsp::ServerCapabilities {
15312            completion_provider: Some(lsp::CompletionOptions {
15313                trigger_characters: Some(vec![".".to_string()]),
15314                resolve_provider: Some(true),
15315                ..Default::default()
15316            }),
15317            ..Default::default()
15318        },
15319        cx,
15320    )
15321    .await;
15322
15323    cx.set_state("fn main() { let a = 2ˇ; }");
15324    cx.simulate_keystroke(".");
15325    let completion_item = lsp::CompletionItem {
15326        label: "Some".into(),
15327        kind: Some(lsp::CompletionItemKind::SNIPPET),
15328        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15329        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15330            kind: lsp::MarkupKind::Markdown,
15331            value: "```rust\nSome(2)\n```".to_string(),
15332        })),
15333        deprecated: Some(false),
15334        sort_text: Some("Some".to_string()),
15335        filter_text: Some("Some".to_string()),
15336        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15337        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15338            range: lsp::Range {
15339                start: lsp::Position {
15340                    line: 0,
15341                    character: 22,
15342                },
15343                end: lsp::Position {
15344                    line: 0,
15345                    character: 22,
15346                },
15347            },
15348            new_text: "Some(2)".to_string(),
15349        })),
15350        additional_text_edits: Some(vec![lsp::TextEdit {
15351            range: lsp::Range {
15352                start: lsp::Position {
15353                    line: 0,
15354                    character: 20,
15355                },
15356                end: lsp::Position {
15357                    line: 0,
15358                    character: 22,
15359                },
15360            },
15361            new_text: "".to_string(),
15362        }]),
15363        ..Default::default()
15364    };
15365
15366    let closure_completion_item = completion_item.clone();
15367    let counter = Arc::new(AtomicUsize::new(0));
15368    let counter_clone = counter.clone();
15369    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15370        let task_completion_item = closure_completion_item.clone();
15371        counter_clone.fetch_add(1, atomic::Ordering::Release);
15372        async move {
15373            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15374                is_incomplete: true,
15375                item_defaults: None,
15376                items: vec![task_completion_item],
15377            })))
15378        }
15379    });
15380
15381    cx.condition(|editor, _| editor.context_menu_visible())
15382        .await;
15383    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15384    assert!(request.next().await.is_some());
15385    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15386
15387    cx.simulate_keystrokes("S o m");
15388    cx.condition(|editor, _| editor.context_menu_visible())
15389        .await;
15390    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15391    assert!(request.next().await.is_some());
15392    assert!(request.next().await.is_some());
15393    assert!(request.next().await.is_some());
15394    request.close();
15395    assert!(request.next().await.is_none());
15396    assert_eq!(
15397        counter.load(atomic::Ordering::Acquire),
15398        4,
15399        "With the completions menu open, only one LSP request should happen per input"
15400    );
15401}
15402
15403#[gpui::test]
15404async fn test_toggle_comment(cx: &mut TestAppContext) {
15405    init_test(cx, |_| {});
15406    let mut cx = EditorTestContext::new(cx).await;
15407    let language = Arc::new(Language::new(
15408        LanguageConfig {
15409            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15410            ..Default::default()
15411        },
15412        Some(tree_sitter_rust::LANGUAGE.into()),
15413    ));
15414    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15415
15416    // If multiple selections intersect a line, the line is only toggled once.
15417    cx.set_state(indoc! {"
15418        fn a() {
15419            «//b();
15420            ˇ»// «c();
15421            //ˇ»  d();
15422        }
15423    "});
15424
15425    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15426
15427    cx.assert_editor_state(indoc! {"
15428        fn a() {
15429            «b();
15430            c();
15431            ˇ» d();
15432        }
15433    "});
15434
15435    // The comment prefix is inserted at the same column for every line in a
15436    // selection.
15437    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15438
15439    cx.assert_editor_state(indoc! {"
15440        fn a() {
15441            // «b();
15442            // c();
15443            ˇ»//  d();
15444        }
15445    "});
15446
15447    // If a selection ends at the beginning of a line, that line is not toggled.
15448    cx.set_selections_state(indoc! {"
15449        fn a() {
15450            // b();
15451            «// c();
15452        ˇ»    //  d();
15453        }
15454    "});
15455
15456    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15457
15458    cx.assert_editor_state(indoc! {"
15459        fn a() {
15460            // b();
15461            «c();
15462        ˇ»    //  d();
15463        }
15464    "});
15465
15466    // If a selection span a single line and is empty, the line is toggled.
15467    cx.set_state(indoc! {"
15468        fn a() {
15469            a();
15470            b();
15471        ˇ
15472        }
15473    "});
15474
15475    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15476
15477    cx.assert_editor_state(indoc! {"
15478        fn a() {
15479            a();
15480            b();
15481        //•ˇ
15482        }
15483    "});
15484
15485    // If a selection span multiple lines, empty lines are not toggled.
15486    cx.set_state(indoc! {"
15487        fn a() {
15488            «a();
15489
15490            c();ˇ»
15491        }
15492    "});
15493
15494    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15495
15496    cx.assert_editor_state(indoc! {"
15497        fn a() {
15498            // «a();
15499
15500            // c();ˇ»
15501        }
15502    "});
15503
15504    // If a selection includes multiple comment prefixes, all lines are uncommented.
15505    cx.set_state(indoc! {"
15506        fn a() {
15507            «// a();
15508            /// b();
15509            //! c();ˇ»
15510        }
15511    "});
15512
15513    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15514
15515    cx.assert_editor_state(indoc! {"
15516        fn a() {
15517            «a();
15518            b();
15519            c();ˇ»
15520        }
15521    "});
15522}
15523
15524#[gpui::test]
15525async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15526    init_test(cx, |_| {});
15527    let mut cx = EditorTestContext::new(cx).await;
15528    let language = Arc::new(Language::new(
15529        LanguageConfig {
15530            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15531            ..Default::default()
15532        },
15533        Some(tree_sitter_rust::LANGUAGE.into()),
15534    ));
15535    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15536
15537    let toggle_comments = &ToggleComments {
15538        advance_downwards: false,
15539        ignore_indent: true,
15540    };
15541
15542    // If multiple selections intersect a line, the line is only toggled once.
15543    cx.set_state(indoc! {"
15544        fn a() {
15545        //    «b();
15546        //    c();
15547        //    ˇ» d();
15548        }
15549    "});
15550
15551    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15552
15553    cx.assert_editor_state(indoc! {"
15554        fn a() {
15555            «b();
15556            c();
15557            ˇ» d();
15558        }
15559    "});
15560
15561    // The comment prefix is inserted at the beginning of each line
15562    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15563
15564    cx.assert_editor_state(indoc! {"
15565        fn a() {
15566        //    «b();
15567        //    c();
15568        //    ˇ» d();
15569        }
15570    "});
15571
15572    // If a selection ends at the beginning of a line, that line is not toggled.
15573    cx.set_selections_state(indoc! {"
15574        fn a() {
15575        //    b();
15576        //    «c();
15577        ˇ»//     d();
15578        }
15579    "});
15580
15581    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15582
15583    cx.assert_editor_state(indoc! {"
15584        fn a() {
15585        //    b();
15586            «c();
15587        ˇ»//     d();
15588        }
15589    "});
15590
15591    // If a selection span a single line and is empty, the line is toggled.
15592    cx.set_state(indoc! {"
15593        fn a() {
15594            a();
15595            b();
15596        ˇ
15597        }
15598    "});
15599
15600    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15601
15602    cx.assert_editor_state(indoc! {"
15603        fn a() {
15604            a();
15605            b();
15606        //ˇ
15607        }
15608    "});
15609
15610    // If a selection span multiple lines, empty lines are not toggled.
15611    cx.set_state(indoc! {"
15612        fn a() {
15613            «a();
15614
15615            c();ˇ»
15616        }
15617    "});
15618
15619    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15620
15621    cx.assert_editor_state(indoc! {"
15622        fn a() {
15623        //    «a();
15624
15625        //    c();ˇ»
15626        }
15627    "});
15628
15629    // If a selection includes multiple comment prefixes, all lines are uncommented.
15630    cx.set_state(indoc! {"
15631        fn a() {
15632        //    «a();
15633        ///    b();
15634        //!    c();ˇ»
15635        }
15636    "});
15637
15638    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15639
15640    cx.assert_editor_state(indoc! {"
15641        fn a() {
15642            «a();
15643            b();
15644            c();ˇ»
15645        }
15646    "});
15647}
15648
15649#[gpui::test]
15650async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15651    init_test(cx, |_| {});
15652
15653    let language = Arc::new(Language::new(
15654        LanguageConfig {
15655            line_comments: vec!["// ".into()],
15656            ..Default::default()
15657        },
15658        Some(tree_sitter_rust::LANGUAGE.into()),
15659    ));
15660
15661    let mut cx = EditorTestContext::new(cx).await;
15662
15663    cx.language_registry().add(language.clone());
15664    cx.update_buffer(|buffer, cx| {
15665        buffer.set_language(Some(language), cx);
15666    });
15667
15668    let toggle_comments = &ToggleComments {
15669        advance_downwards: true,
15670        ignore_indent: false,
15671    };
15672
15673    // Single cursor on one line -> advance
15674    // Cursor moves horizontally 3 characters as well on non-blank line
15675    cx.set_state(indoc!(
15676        "fn a() {
15677             ˇdog();
15678             cat();
15679        }"
15680    ));
15681    cx.update_editor(|editor, window, cx| {
15682        editor.toggle_comments(toggle_comments, window, cx);
15683    });
15684    cx.assert_editor_state(indoc!(
15685        "fn a() {
15686             // dog();
15687             catˇ();
15688        }"
15689    ));
15690
15691    // Single selection on one line -> don't advance
15692    cx.set_state(indoc!(
15693        "fn a() {
15694             «dog()ˇ»;
15695             cat();
15696        }"
15697    ));
15698    cx.update_editor(|editor, window, cx| {
15699        editor.toggle_comments(toggle_comments, window, cx);
15700    });
15701    cx.assert_editor_state(indoc!(
15702        "fn a() {
15703             // «dog()ˇ»;
15704             cat();
15705        }"
15706    ));
15707
15708    // Multiple cursors on one line -> advance
15709    cx.set_state(indoc!(
15710        "fn a() {
15711             ˇdˇog();
15712             cat();
15713        }"
15714    ));
15715    cx.update_editor(|editor, window, cx| {
15716        editor.toggle_comments(toggle_comments, window, cx);
15717    });
15718    cx.assert_editor_state(indoc!(
15719        "fn a() {
15720             // dog();
15721             catˇ(ˇ);
15722        }"
15723    ));
15724
15725    // Multiple cursors on one line, with selection -> don't advance
15726    cx.set_state(indoc!(
15727        "fn a() {
15728             ˇdˇog«()ˇ»;
15729             cat();
15730        }"
15731    ));
15732    cx.update_editor(|editor, window, cx| {
15733        editor.toggle_comments(toggle_comments, window, cx);
15734    });
15735    cx.assert_editor_state(indoc!(
15736        "fn a() {
15737             // ˇdˇog«()ˇ»;
15738             cat();
15739        }"
15740    ));
15741
15742    // Single cursor on one line -> advance
15743    // Cursor moves to column 0 on blank line
15744    cx.set_state(indoc!(
15745        "fn a() {
15746             ˇdog();
15747
15748             cat();
15749        }"
15750    ));
15751    cx.update_editor(|editor, window, cx| {
15752        editor.toggle_comments(toggle_comments, window, cx);
15753    });
15754    cx.assert_editor_state(indoc!(
15755        "fn a() {
15756             // dog();
15757        ˇ
15758             cat();
15759        }"
15760    ));
15761
15762    // Single cursor on one line -> advance
15763    // Cursor starts and ends at column 0
15764    cx.set_state(indoc!(
15765        "fn a() {
15766         ˇ    dog();
15767             cat();
15768        }"
15769    ));
15770    cx.update_editor(|editor, window, cx| {
15771        editor.toggle_comments(toggle_comments, window, cx);
15772    });
15773    cx.assert_editor_state(indoc!(
15774        "fn a() {
15775             // dog();
15776         ˇ    cat();
15777        }"
15778    ));
15779}
15780
15781#[gpui::test]
15782async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15783    init_test(cx, |_| {});
15784
15785    let mut cx = EditorTestContext::new(cx).await;
15786
15787    let html_language = Arc::new(
15788        Language::new(
15789            LanguageConfig {
15790                name: "HTML".into(),
15791                block_comment: Some(BlockCommentConfig {
15792                    start: "<!-- ".into(),
15793                    prefix: "".into(),
15794                    end: " -->".into(),
15795                    tab_size: 0,
15796                }),
15797                ..Default::default()
15798            },
15799            Some(tree_sitter_html::LANGUAGE.into()),
15800        )
15801        .with_injection_query(
15802            r#"
15803            (script_element
15804                (raw_text) @injection.content
15805                (#set! injection.language "javascript"))
15806            "#,
15807        )
15808        .unwrap(),
15809    );
15810
15811    let javascript_language = Arc::new(Language::new(
15812        LanguageConfig {
15813            name: "JavaScript".into(),
15814            line_comments: vec!["// ".into()],
15815            ..Default::default()
15816        },
15817        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15818    ));
15819
15820    cx.language_registry().add(html_language.clone());
15821    cx.language_registry().add(javascript_language);
15822    cx.update_buffer(|buffer, cx| {
15823        buffer.set_language(Some(html_language), cx);
15824    });
15825
15826    // Toggle comments for empty selections
15827    cx.set_state(
15828        &r#"
15829            <p>A</p>ˇ
15830            <p>B</p>ˇ
15831            <p>C</p>ˇ
15832        "#
15833        .unindent(),
15834    );
15835    cx.update_editor(|editor, window, cx| {
15836        editor.toggle_comments(&ToggleComments::default(), window, cx)
15837    });
15838    cx.assert_editor_state(
15839        &r#"
15840            <!-- <p>A</p>ˇ -->
15841            <!-- <p>B</p>ˇ -->
15842            <!-- <p>C</p>ˇ -->
15843        "#
15844        .unindent(),
15845    );
15846    cx.update_editor(|editor, window, cx| {
15847        editor.toggle_comments(&ToggleComments::default(), window, cx)
15848    });
15849    cx.assert_editor_state(
15850        &r#"
15851            <p>A</p>ˇ
15852            <p>B</p>ˇ
15853            <p>C</p>ˇ
15854        "#
15855        .unindent(),
15856    );
15857
15858    // Toggle comments for mixture of empty and non-empty selections, where
15859    // multiple selections occupy a given line.
15860    cx.set_state(
15861        &r#"
15862            <p>A«</p>
15863            <p>ˇ»B</p>ˇ
15864            <p>C«</p>
15865            <p>ˇ»D</p>ˇ
15866        "#
15867        .unindent(),
15868    );
15869
15870    cx.update_editor(|editor, window, cx| {
15871        editor.toggle_comments(&ToggleComments::default(), window, cx)
15872    });
15873    cx.assert_editor_state(
15874        &r#"
15875            <!-- <p>A«</p>
15876            <p>ˇ»B</p>ˇ -->
15877            <!-- <p>C«</p>
15878            <p>ˇ»D</p>ˇ -->
15879        "#
15880        .unindent(),
15881    );
15882    cx.update_editor(|editor, window, cx| {
15883        editor.toggle_comments(&ToggleComments::default(), window, cx)
15884    });
15885    cx.assert_editor_state(
15886        &r#"
15887            <p>A«</p>
15888            <p>ˇ»B</p>ˇ
15889            <p>C«</p>
15890            <p>ˇ»D</p>ˇ
15891        "#
15892        .unindent(),
15893    );
15894
15895    // Toggle comments when different languages are active for different
15896    // selections.
15897    cx.set_state(
15898        &r#"
15899            ˇ<script>
15900                ˇvar x = new Y();
15901            ˇ</script>
15902        "#
15903        .unindent(),
15904    );
15905    cx.executor().run_until_parked();
15906    cx.update_editor(|editor, window, cx| {
15907        editor.toggle_comments(&ToggleComments::default(), window, cx)
15908    });
15909    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15910    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15911    cx.assert_editor_state(
15912        &r#"
15913            <!-- ˇ<script> -->
15914                // ˇvar x = new Y();
15915            <!-- ˇ</script> -->
15916        "#
15917        .unindent(),
15918    );
15919}
15920
15921#[gpui::test]
15922fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15923    init_test(cx, |_| {});
15924
15925    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15926    let multibuffer = cx.new(|cx| {
15927        let mut multibuffer = MultiBuffer::new(ReadWrite);
15928        multibuffer.push_excerpts(
15929            buffer.clone(),
15930            [
15931                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15932                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15933            ],
15934            cx,
15935        );
15936        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15937        multibuffer
15938    });
15939
15940    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15941    editor.update_in(cx, |editor, window, cx| {
15942        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15944            s.select_ranges([
15945                Point::new(0, 0)..Point::new(0, 0),
15946                Point::new(1, 0)..Point::new(1, 0),
15947            ])
15948        });
15949
15950        editor.handle_input("X", window, cx);
15951        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15952        assert_eq!(
15953            editor.selections.ranges(cx),
15954            [
15955                Point::new(0, 1)..Point::new(0, 1),
15956                Point::new(1, 1)..Point::new(1, 1),
15957            ]
15958        );
15959
15960        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15962            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15963        });
15964        editor.backspace(&Default::default(), window, cx);
15965        assert_eq!(editor.text(cx), "Xa\nbbb");
15966        assert_eq!(
15967            editor.selections.ranges(cx),
15968            [Point::new(1, 0)..Point::new(1, 0)]
15969        );
15970
15971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15972            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15973        });
15974        editor.backspace(&Default::default(), window, cx);
15975        assert_eq!(editor.text(cx), "X\nbb");
15976        assert_eq!(
15977            editor.selections.ranges(cx),
15978            [Point::new(0, 1)..Point::new(0, 1)]
15979        );
15980    });
15981}
15982
15983#[gpui::test]
15984fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15985    init_test(cx, |_| {});
15986
15987    let markers = vec![('[', ']').into(), ('(', ')').into()];
15988    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15989        indoc! {"
15990            [aaaa
15991            (bbbb]
15992            cccc)",
15993        },
15994        markers.clone(),
15995    );
15996    let excerpt_ranges = markers.into_iter().map(|marker| {
15997        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15998        ExcerptRange::new(context)
15999    });
16000    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16001    let multibuffer = cx.new(|cx| {
16002        let mut multibuffer = MultiBuffer::new(ReadWrite);
16003        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16004        multibuffer
16005    });
16006
16007    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16008    editor.update_in(cx, |editor, window, cx| {
16009        let (expected_text, selection_ranges) = marked_text_ranges(
16010            indoc! {"
16011                aaaa
16012                bˇbbb
16013                bˇbbˇb
16014                cccc"
16015            },
16016            true,
16017        );
16018        assert_eq!(editor.text(cx), expected_text);
16019        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16020            s.select_ranges(selection_ranges)
16021        });
16022
16023        editor.handle_input("X", window, cx);
16024
16025        let (expected_text, expected_selections) = marked_text_ranges(
16026            indoc! {"
16027                aaaa
16028                bXˇbbXb
16029                bXˇbbXˇb
16030                cccc"
16031            },
16032            false,
16033        );
16034        assert_eq!(editor.text(cx), expected_text);
16035        assert_eq!(editor.selections.ranges(cx), expected_selections);
16036
16037        editor.newline(&Newline, window, cx);
16038        let (expected_text, expected_selections) = marked_text_ranges(
16039            indoc! {"
16040                aaaa
16041                bX
16042                ˇbbX
16043                b
16044                bX
16045                ˇbbX
16046                ˇb
16047                cccc"
16048            },
16049            false,
16050        );
16051        assert_eq!(editor.text(cx), expected_text);
16052        assert_eq!(editor.selections.ranges(cx), expected_selections);
16053    });
16054}
16055
16056#[gpui::test]
16057fn test_refresh_selections(cx: &mut TestAppContext) {
16058    init_test(cx, |_| {});
16059
16060    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16061    let mut excerpt1_id = None;
16062    let multibuffer = cx.new(|cx| {
16063        let mut multibuffer = MultiBuffer::new(ReadWrite);
16064        excerpt1_id = multibuffer
16065            .push_excerpts(
16066                buffer.clone(),
16067                [
16068                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16069                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16070                ],
16071                cx,
16072            )
16073            .into_iter()
16074            .next();
16075        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16076        multibuffer
16077    });
16078
16079    let editor = cx.add_window(|window, cx| {
16080        let mut editor = build_editor(multibuffer.clone(), window, cx);
16081        let snapshot = editor.snapshot(window, cx);
16082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16083            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16084        });
16085        editor.begin_selection(
16086            Point::new(2, 1).to_display_point(&snapshot),
16087            true,
16088            1,
16089            window,
16090            cx,
16091        );
16092        assert_eq!(
16093            editor.selections.ranges(cx),
16094            [
16095                Point::new(1, 3)..Point::new(1, 3),
16096                Point::new(2, 1)..Point::new(2, 1),
16097            ]
16098        );
16099        editor
16100    });
16101
16102    // Refreshing selections is a no-op when excerpts haven't changed.
16103    _ = editor.update(cx, |editor, window, cx| {
16104        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16105        assert_eq!(
16106            editor.selections.ranges(cx),
16107            [
16108                Point::new(1, 3)..Point::new(1, 3),
16109                Point::new(2, 1)..Point::new(2, 1),
16110            ]
16111        );
16112    });
16113
16114    multibuffer.update(cx, |multibuffer, cx| {
16115        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16116    });
16117    _ = editor.update(cx, |editor, window, cx| {
16118        // Removing an excerpt causes the first selection to become degenerate.
16119        assert_eq!(
16120            editor.selections.ranges(cx),
16121            [
16122                Point::new(0, 0)..Point::new(0, 0),
16123                Point::new(0, 1)..Point::new(0, 1)
16124            ]
16125        );
16126
16127        // Refreshing selections will relocate the first selection to the original buffer
16128        // location.
16129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16130        assert_eq!(
16131            editor.selections.ranges(cx),
16132            [
16133                Point::new(0, 1)..Point::new(0, 1),
16134                Point::new(0, 3)..Point::new(0, 3)
16135            ]
16136        );
16137        assert!(editor.selections.pending_anchor().is_some());
16138    });
16139}
16140
16141#[gpui::test]
16142fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16143    init_test(cx, |_| {});
16144
16145    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16146    let mut excerpt1_id = None;
16147    let multibuffer = cx.new(|cx| {
16148        let mut multibuffer = MultiBuffer::new(ReadWrite);
16149        excerpt1_id = multibuffer
16150            .push_excerpts(
16151                buffer.clone(),
16152                [
16153                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16154                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16155                ],
16156                cx,
16157            )
16158            .into_iter()
16159            .next();
16160        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16161        multibuffer
16162    });
16163
16164    let editor = cx.add_window(|window, cx| {
16165        let mut editor = build_editor(multibuffer.clone(), window, cx);
16166        let snapshot = editor.snapshot(window, cx);
16167        editor.begin_selection(
16168            Point::new(1, 3).to_display_point(&snapshot),
16169            false,
16170            1,
16171            window,
16172            cx,
16173        );
16174        assert_eq!(
16175            editor.selections.ranges(cx),
16176            [Point::new(1, 3)..Point::new(1, 3)]
16177        );
16178        editor
16179    });
16180
16181    multibuffer.update(cx, |multibuffer, cx| {
16182        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16183    });
16184    _ = editor.update(cx, |editor, window, cx| {
16185        assert_eq!(
16186            editor.selections.ranges(cx),
16187            [Point::new(0, 0)..Point::new(0, 0)]
16188        );
16189
16190        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16192        assert_eq!(
16193            editor.selections.ranges(cx),
16194            [Point::new(0, 3)..Point::new(0, 3)]
16195        );
16196        assert!(editor.selections.pending_anchor().is_some());
16197    });
16198}
16199
16200#[gpui::test]
16201async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16202    init_test(cx, |_| {});
16203
16204    let language = Arc::new(
16205        Language::new(
16206            LanguageConfig {
16207                brackets: BracketPairConfig {
16208                    pairs: vec![
16209                        BracketPair {
16210                            start: "{".to_string(),
16211                            end: "}".to_string(),
16212                            close: true,
16213                            surround: true,
16214                            newline: true,
16215                        },
16216                        BracketPair {
16217                            start: "/* ".to_string(),
16218                            end: " */".to_string(),
16219                            close: true,
16220                            surround: true,
16221                            newline: true,
16222                        },
16223                    ],
16224                    ..Default::default()
16225                },
16226                ..Default::default()
16227            },
16228            Some(tree_sitter_rust::LANGUAGE.into()),
16229        )
16230        .with_indents_query("")
16231        .unwrap(),
16232    );
16233
16234    let text = concat!(
16235        "{   }\n",     //
16236        "  x\n",       //
16237        "  /*   */\n", //
16238        "x\n",         //
16239        "{{} }\n",     //
16240    );
16241
16242    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16243    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16244    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16245    editor
16246        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16247        .await;
16248
16249    editor.update_in(cx, |editor, window, cx| {
16250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16251            s.select_display_ranges([
16252                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16253                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16254                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16255            ])
16256        });
16257        editor.newline(&Newline, window, cx);
16258
16259        assert_eq!(
16260            editor.buffer().read(cx).read(cx).text(),
16261            concat!(
16262                "{ \n",    // Suppress rustfmt
16263                "\n",      //
16264                "}\n",     //
16265                "  x\n",   //
16266                "  /* \n", //
16267                "  \n",    //
16268                "  */\n",  //
16269                "x\n",     //
16270                "{{} \n",  //
16271                "}\n",     //
16272            )
16273        );
16274    });
16275}
16276
16277#[gpui::test]
16278fn test_highlighted_ranges(cx: &mut TestAppContext) {
16279    init_test(cx, |_| {});
16280
16281    let editor = cx.add_window(|window, cx| {
16282        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16283        build_editor(buffer, window, cx)
16284    });
16285
16286    _ = editor.update(cx, |editor, window, cx| {
16287        struct Type1;
16288        struct Type2;
16289
16290        let buffer = editor.buffer.read(cx).snapshot(cx);
16291
16292        let anchor_range =
16293            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16294
16295        editor.highlight_background::<Type1>(
16296            &[
16297                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16298                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16299                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16300                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16301            ],
16302            |_| Hsla::red(),
16303            cx,
16304        );
16305        editor.highlight_background::<Type2>(
16306            &[
16307                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16308                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16309                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16310                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16311            ],
16312            |_| Hsla::green(),
16313            cx,
16314        );
16315
16316        let snapshot = editor.snapshot(window, cx);
16317        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16318            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16319            &snapshot,
16320            cx.theme(),
16321        );
16322        assert_eq!(
16323            highlighted_ranges,
16324            &[
16325                (
16326                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16327                    Hsla::green(),
16328                ),
16329                (
16330                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16331                    Hsla::red(),
16332                ),
16333                (
16334                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16335                    Hsla::green(),
16336                ),
16337                (
16338                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16339                    Hsla::red(),
16340                ),
16341            ]
16342        );
16343        assert_eq!(
16344            editor.sorted_background_highlights_in_range(
16345                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16346                &snapshot,
16347                cx.theme(),
16348            ),
16349            &[(
16350                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16351                Hsla::red(),
16352            )]
16353        );
16354    });
16355}
16356
16357#[gpui::test]
16358async fn test_following(cx: &mut TestAppContext) {
16359    init_test(cx, |_| {});
16360
16361    let fs = FakeFs::new(cx.executor());
16362    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16363
16364    let buffer = project.update(cx, |project, cx| {
16365        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16366        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16367    });
16368    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16369    let follower = cx.update(|cx| {
16370        cx.open_window(
16371            WindowOptions {
16372                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16373                    gpui::Point::new(px(0.), px(0.)),
16374                    gpui::Point::new(px(10.), px(80.)),
16375                ))),
16376                ..Default::default()
16377            },
16378            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16379        )
16380        .unwrap()
16381    });
16382
16383    let is_still_following = Rc::new(RefCell::new(true));
16384    let follower_edit_event_count = Rc::new(RefCell::new(0));
16385    let pending_update = Rc::new(RefCell::new(None));
16386    let leader_entity = leader.root(cx).unwrap();
16387    let follower_entity = follower.root(cx).unwrap();
16388    _ = follower.update(cx, {
16389        let update = pending_update.clone();
16390        let is_still_following = is_still_following.clone();
16391        let follower_edit_event_count = follower_edit_event_count.clone();
16392        |_, window, cx| {
16393            cx.subscribe_in(
16394                &leader_entity,
16395                window,
16396                move |_, leader, event, window, cx| {
16397                    leader.read(cx).add_event_to_update_proto(
16398                        event,
16399                        &mut update.borrow_mut(),
16400                        window,
16401                        cx,
16402                    );
16403                },
16404            )
16405            .detach();
16406
16407            cx.subscribe_in(
16408                &follower_entity,
16409                window,
16410                move |_, _, event: &EditorEvent, _window, _cx| {
16411                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16412                        *is_still_following.borrow_mut() = false;
16413                    }
16414
16415                    if let EditorEvent::BufferEdited = event {
16416                        *follower_edit_event_count.borrow_mut() += 1;
16417                    }
16418                },
16419            )
16420            .detach();
16421        }
16422    });
16423
16424    // Update the selections only
16425    _ = leader.update(cx, |leader, window, cx| {
16426        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16427            s.select_ranges([1..1])
16428        });
16429    });
16430    follower
16431        .update(cx, |follower, window, cx| {
16432            follower.apply_update_proto(
16433                &project,
16434                pending_update.borrow_mut().take().unwrap(),
16435                window,
16436                cx,
16437            )
16438        })
16439        .unwrap()
16440        .await
16441        .unwrap();
16442    _ = follower.update(cx, |follower, _, cx| {
16443        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16444    });
16445    assert!(*is_still_following.borrow());
16446    assert_eq!(*follower_edit_event_count.borrow(), 0);
16447
16448    // Update the scroll position only
16449    _ = leader.update(cx, |leader, window, cx| {
16450        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16451    });
16452    follower
16453        .update(cx, |follower, window, cx| {
16454            follower.apply_update_proto(
16455                &project,
16456                pending_update.borrow_mut().take().unwrap(),
16457                window,
16458                cx,
16459            )
16460        })
16461        .unwrap()
16462        .await
16463        .unwrap();
16464    assert_eq!(
16465        follower
16466            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16467            .unwrap(),
16468        gpui::Point::new(1.5, 3.5)
16469    );
16470    assert!(*is_still_following.borrow());
16471    assert_eq!(*follower_edit_event_count.borrow(), 0);
16472
16473    // Update the selections and scroll position. The follower's scroll position is updated
16474    // via autoscroll, not via the leader's exact scroll position.
16475    _ = leader.update(cx, |leader, window, cx| {
16476        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16477            s.select_ranges([0..0])
16478        });
16479        leader.request_autoscroll(Autoscroll::newest(), cx);
16480        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16481    });
16482    follower
16483        .update(cx, |follower, window, cx| {
16484            follower.apply_update_proto(
16485                &project,
16486                pending_update.borrow_mut().take().unwrap(),
16487                window,
16488                cx,
16489            )
16490        })
16491        .unwrap()
16492        .await
16493        .unwrap();
16494    _ = follower.update(cx, |follower, _, cx| {
16495        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16496        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16497    });
16498    assert!(*is_still_following.borrow());
16499
16500    // Creating a pending selection that precedes another selection
16501    _ = leader.update(cx, |leader, window, cx| {
16502        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16503            s.select_ranges([1..1])
16504        });
16505        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16506    });
16507    follower
16508        .update(cx, |follower, window, cx| {
16509            follower.apply_update_proto(
16510                &project,
16511                pending_update.borrow_mut().take().unwrap(),
16512                window,
16513                cx,
16514            )
16515        })
16516        .unwrap()
16517        .await
16518        .unwrap();
16519    _ = follower.update(cx, |follower, _, cx| {
16520        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16521    });
16522    assert!(*is_still_following.borrow());
16523
16524    // Extend the pending selection so that it surrounds another selection
16525    _ = leader.update(cx, |leader, window, cx| {
16526        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16527    });
16528    follower
16529        .update(cx, |follower, window, cx| {
16530            follower.apply_update_proto(
16531                &project,
16532                pending_update.borrow_mut().take().unwrap(),
16533                window,
16534                cx,
16535            )
16536        })
16537        .unwrap()
16538        .await
16539        .unwrap();
16540    _ = follower.update(cx, |follower, _, cx| {
16541        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16542    });
16543
16544    // Scrolling locally breaks the follow
16545    _ = follower.update(cx, |follower, window, cx| {
16546        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16547        follower.set_scroll_anchor(
16548            ScrollAnchor {
16549                anchor: top_anchor,
16550                offset: gpui::Point::new(0.0, 0.5),
16551            },
16552            window,
16553            cx,
16554        );
16555    });
16556    assert!(!(*is_still_following.borrow()));
16557}
16558
16559#[gpui::test]
16560async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16561    init_test(cx, |_| {});
16562
16563    let fs = FakeFs::new(cx.executor());
16564    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16565    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16566    let pane = workspace
16567        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16568        .unwrap();
16569
16570    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16571
16572    let leader = pane.update_in(cx, |_, window, cx| {
16573        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16574        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16575    });
16576
16577    // Start following the editor when it has no excerpts.
16578    let mut state_message =
16579        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16580    let workspace_entity = workspace.root(cx).unwrap();
16581    let follower_1 = cx
16582        .update_window(*workspace.deref(), |_, window, cx| {
16583            Editor::from_state_proto(
16584                workspace_entity,
16585                ViewId {
16586                    creator: CollaboratorId::PeerId(PeerId::default()),
16587                    id: 0,
16588                },
16589                &mut state_message,
16590                window,
16591                cx,
16592            )
16593        })
16594        .unwrap()
16595        .unwrap()
16596        .await
16597        .unwrap();
16598
16599    let update_message = Rc::new(RefCell::new(None));
16600    follower_1.update_in(cx, {
16601        let update = update_message.clone();
16602        |_, window, cx| {
16603            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16604                leader.read(cx).add_event_to_update_proto(
16605                    event,
16606                    &mut update.borrow_mut(),
16607                    window,
16608                    cx,
16609                );
16610            })
16611            .detach();
16612        }
16613    });
16614
16615    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16616        (
16617            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16618            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16619        )
16620    });
16621
16622    // Insert some excerpts.
16623    leader.update(cx, |leader, cx| {
16624        leader.buffer.update(cx, |multibuffer, cx| {
16625            multibuffer.set_excerpts_for_path(
16626                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16627                buffer_1.clone(),
16628                vec![
16629                    Point::row_range(0..3),
16630                    Point::row_range(1..6),
16631                    Point::row_range(12..15),
16632                ],
16633                0,
16634                cx,
16635            );
16636            multibuffer.set_excerpts_for_path(
16637                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16638                buffer_2.clone(),
16639                vec![Point::row_range(0..6), Point::row_range(8..12)],
16640                0,
16641                cx,
16642            );
16643        });
16644    });
16645
16646    // Apply the update of adding the excerpts.
16647    follower_1
16648        .update_in(cx, |follower, window, cx| {
16649            follower.apply_update_proto(
16650                &project,
16651                update_message.borrow().clone().unwrap(),
16652                window,
16653                cx,
16654            )
16655        })
16656        .await
16657        .unwrap();
16658    assert_eq!(
16659        follower_1.update(cx, |editor, cx| editor.text(cx)),
16660        leader.update(cx, |editor, cx| editor.text(cx))
16661    );
16662    update_message.borrow_mut().take();
16663
16664    // Start following separately after it already has excerpts.
16665    let mut state_message =
16666        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16667    let workspace_entity = workspace.root(cx).unwrap();
16668    let follower_2 = cx
16669        .update_window(*workspace.deref(), |_, window, cx| {
16670            Editor::from_state_proto(
16671                workspace_entity,
16672                ViewId {
16673                    creator: CollaboratorId::PeerId(PeerId::default()),
16674                    id: 0,
16675                },
16676                &mut state_message,
16677                window,
16678                cx,
16679            )
16680        })
16681        .unwrap()
16682        .unwrap()
16683        .await
16684        .unwrap();
16685    assert_eq!(
16686        follower_2.update(cx, |editor, cx| editor.text(cx)),
16687        leader.update(cx, |editor, cx| editor.text(cx))
16688    );
16689
16690    // Remove some excerpts.
16691    leader.update(cx, |leader, cx| {
16692        leader.buffer.update(cx, |multibuffer, cx| {
16693            let excerpt_ids = multibuffer.excerpt_ids();
16694            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16695            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16696        });
16697    });
16698
16699    // Apply the update of removing the excerpts.
16700    follower_1
16701        .update_in(cx, |follower, window, cx| {
16702            follower.apply_update_proto(
16703                &project,
16704                update_message.borrow().clone().unwrap(),
16705                window,
16706                cx,
16707            )
16708        })
16709        .await
16710        .unwrap();
16711    follower_2
16712        .update_in(cx, |follower, window, cx| {
16713            follower.apply_update_proto(
16714                &project,
16715                update_message.borrow().clone().unwrap(),
16716                window,
16717                cx,
16718            )
16719        })
16720        .await
16721        .unwrap();
16722    update_message.borrow_mut().take();
16723    assert_eq!(
16724        follower_1.update(cx, |editor, cx| editor.text(cx)),
16725        leader.update(cx, |editor, cx| editor.text(cx))
16726    );
16727}
16728
16729#[gpui::test]
16730async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16731    init_test(cx, |_| {});
16732
16733    let mut cx = EditorTestContext::new(cx).await;
16734    let lsp_store =
16735        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16736
16737    cx.set_state(indoc! {"
16738        ˇfn func(abc def: i32) -> u32 {
16739        }
16740    "});
16741
16742    cx.update(|_, cx| {
16743        lsp_store.update(cx, |lsp_store, cx| {
16744            lsp_store
16745                .update_diagnostics(
16746                    LanguageServerId(0),
16747                    lsp::PublishDiagnosticsParams {
16748                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16749                        version: None,
16750                        diagnostics: vec![
16751                            lsp::Diagnostic {
16752                                range: lsp::Range::new(
16753                                    lsp::Position::new(0, 11),
16754                                    lsp::Position::new(0, 12),
16755                                ),
16756                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16757                                ..Default::default()
16758                            },
16759                            lsp::Diagnostic {
16760                                range: lsp::Range::new(
16761                                    lsp::Position::new(0, 12),
16762                                    lsp::Position::new(0, 15),
16763                                ),
16764                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16765                                ..Default::default()
16766                            },
16767                            lsp::Diagnostic {
16768                                range: lsp::Range::new(
16769                                    lsp::Position::new(0, 25),
16770                                    lsp::Position::new(0, 28),
16771                                ),
16772                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16773                                ..Default::default()
16774                            },
16775                        ],
16776                    },
16777                    None,
16778                    DiagnosticSourceKind::Pushed,
16779                    &[],
16780                    cx,
16781                )
16782                .unwrap()
16783        });
16784    });
16785
16786    executor.run_until_parked();
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    cx.update_editor(|editor, window, cx| {
16807        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16808    });
16809
16810    cx.assert_editor_state(indoc! {"
16811        fn func(abcˇ def: i32) -> u32 {
16812        }
16813    "});
16814
16815    cx.update_editor(|editor, window, cx| {
16816        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16817    });
16818
16819    cx.assert_editor_state(indoc! {"
16820        fn func(abc def: i32) -> ˇu32 {
16821        }
16822    "});
16823}
16824
16825#[gpui::test]
16826async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16827    init_test(cx, |_| {});
16828
16829    let mut cx = EditorTestContext::new(cx).await;
16830
16831    let diff_base = r#"
16832        use some::mod;
16833
16834        const A: u32 = 42;
16835
16836        fn main() {
16837            println!("hello");
16838
16839            println!("world");
16840        }
16841        "#
16842    .unindent();
16843
16844    // Edits are modified, removed, modified, added
16845    cx.set_state(
16846        &r#"
16847        use some::modified;
16848
16849        ˇ
16850        fn main() {
16851            println!("hello there");
16852
16853            println!("around the");
16854            println!("world");
16855        }
16856        "#
16857        .unindent(),
16858    );
16859
16860    cx.set_head_text(&diff_base);
16861    executor.run_until_parked();
16862
16863    cx.update_editor(|editor, window, cx| {
16864        //Wrap around the bottom of the buffer
16865        for _ in 0..3 {
16866            editor.go_to_next_hunk(&GoToHunk, window, cx);
16867        }
16868    });
16869
16870    cx.assert_editor_state(
16871        &r#"
16872        ˇuse some::modified;
16873
16874
16875        fn main() {
16876            println!("hello there");
16877
16878            println!("around the");
16879            println!("world");
16880        }
16881        "#
16882        .unindent(),
16883    );
16884
16885    cx.update_editor(|editor, window, cx| {
16886        //Wrap around the top of the buffer
16887        for _ in 0..2 {
16888            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16889        }
16890    });
16891
16892    cx.assert_editor_state(
16893        &r#"
16894        use some::modified;
16895
16896
16897        fn main() {
16898        ˇ    println!("hello there");
16899
16900            println!("around the");
16901            println!("world");
16902        }
16903        "#
16904        .unindent(),
16905    );
16906
16907    cx.update_editor(|editor, window, cx| {
16908        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16909    });
16910
16911    cx.assert_editor_state(
16912        &r#"
16913        use some::modified;
16914
16915        ˇ
16916        fn main() {
16917            println!("hello there");
16918
16919            println!("around the");
16920            println!("world");
16921        }
16922        "#
16923        .unindent(),
16924    );
16925
16926    cx.update_editor(|editor, window, cx| {
16927        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16928    });
16929
16930    cx.assert_editor_state(
16931        &r#"
16932        ˇuse some::modified;
16933
16934
16935        fn main() {
16936            println!("hello there");
16937
16938            println!("around the");
16939            println!("world");
16940        }
16941        "#
16942        .unindent(),
16943    );
16944
16945    cx.update_editor(|editor, window, cx| {
16946        for _ in 0..2 {
16947            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16948        }
16949    });
16950
16951    cx.assert_editor_state(
16952        &r#"
16953        use some::modified;
16954
16955
16956        fn main() {
16957        ˇ    println!("hello there");
16958
16959            println!("around the");
16960            println!("world");
16961        }
16962        "#
16963        .unindent(),
16964    );
16965
16966    cx.update_editor(|editor, window, cx| {
16967        editor.fold(&Fold, window, cx);
16968    });
16969
16970    cx.update_editor(|editor, window, cx| {
16971        editor.go_to_next_hunk(&GoToHunk, window, cx);
16972    });
16973
16974    cx.assert_editor_state(
16975        &r#"
16976        ˇuse some::modified;
16977
16978
16979        fn main() {
16980            println!("hello there");
16981
16982            println!("around the");
16983            println!("world");
16984        }
16985        "#
16986        .unindent(),
16987    );
16988}
16989
16990#[test]
16991fn test_split_words() {
16992    fn split(text: &str) -> Vec<&str> {
16993        split_words(text).collect()
16994    }
16995
16996    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16997    assert_eq!(split("hello_world"), &["hello_", "world"]);
16998    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16999    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17000    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17001    assert_eq!(split("helloworld"), &["helloworld"]);
17002
17003    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17004}
17005
17006#[gpui::test]
17007async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17008    init_test(cx, |_| {});
17009
17010    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17011    let mut assert = |before, after| {
17012        let _state_context = cx.set_state(before);
17013        cx.run_until_parked();
17014        cx.update_editor(|editor, window, cx| {
17015            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17016        });
17017        cx.run_until_parked();
17018        cx.assert_editor_state(after);
17019    };
17020
17021    // Outside bracket jumps to outside of matching bracket
17022    assert("console.logˇ(var);", "console.log(var)ˇ;");
17023    assert("console.log(var)ˇ;", "console.logˇ(var);");
17024
17025    // Inside bracket jumps to inside of matching bracket
17026    assert("console.log(ˇvar);", "console.log(varˇ);");
17027    assert("console.log(varˇ);", "console.log(ˇvar);");
17028
17029    // When outside a bracket and inside, favor jumping to the inside bracket
17030    assert(
17031        "console.log('foo', [1, 2, 3]ˇ);",
17032        "console.log(ˇ'foo', [1, 2, 3]);",
17033    );
17034    assert(
17035        "console.log(ˇ'foo', [1, 2, 3]);",
17036        "console.log('foo', [1, 2, 3]ˇ);",
17037    );
17038
17039    // Bias forward if two options are equally likely
17040    assert(
17041        "let result = curried_fun()ˇ();",
17042        "let result = curried_fun()()ˇ;",
17043    );
17044
17045    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17046    assert(
17047        indoc! {"
17048            function test() {
17049                console.log('test')ˇ
17050            }"},
17051        indoc! {"
17052            function test() {
17053                console.logˇ('test')
17054            }"},
17055    );
17056}
17057
17058#[gpui::test]
17059async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17060    init_test(cx, |_| {});
17061
17062    let fs = FakeFs::new(cx.executor());
17063    fs.insert_tree(
17064        path!("/a"),
17065        json!({
17066            "main.rs": "fn main() { let a = 5; }",
17067            "other.rs": "// Test file",
17068        }),
17069    )
17070    .await;
17071    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17072
17073    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17074    language_registry.add(Arc::new(Language::new(
17075        LanguageConfig {
17076            name: "Rust".into(),
17077            matcher: LanguageMatcher {
17078                path_suffixes: vec!["rs".to_string()],
17079                ..Default::default()
17080            },
17081            brackets: BracketPairConfig {
17082                pairs: vec![BracketPair {
17083                    start: "{".to_string(),
17084                    end: "}".to_string(),
17085                    close: true,
17086                    surround: true,
17087                    newline: true,
17088                }],
17089                disabled_scopes_by_bracket_ix: Vec::new(),
17090            },
17091            ..Default::default()
17092        },
17093        Some(tree_sitter_rust::LANGUAGE.into()),
17094    )));
17095    let mut fake_servers = language_registry.register_fake_lsp(
17096        "Rust",
17097        FakeLspAdapter {
17098            capabilities: lsp::ServerCapabilities {
17099                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17100                    first_trigger_character: "{".to_string(),
17101                    more_trigger_character: None,
17102                }),
17103                ..Default::default()
17104            },
17105            ..Default::default()
17106        },
17107    );
17108
17109    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17110
17111    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17112
17113    let worktree_id = workspace
17114        .update(cx, |workspace, _, cx| {
17115            workspace.project().update(cx, |project, cx| {
17116                project.worktrees(cx).next().unwrap().read(cx).id()
17117            })
17118        })
17119        .unwrap();
17120
17121    let buffer = project
17122        .update(cx, |project, cx| {
17123            project.open_local_buffer(path!("/a/main.rs"), cx)
17124        })
17125        .await
17126        .unwrap();
17127    let editor_handle = workspace
17128        .update(cx, |workspace, window, cx| {
17129            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17130        })
17131        .unwrap()
17132        .await
17133        .unwrap()
17134        .downcast::<Editor>()
17135        .unwrap();
17136
17137    cx.executor().start_waiting();
17138    let fake_server = fake_servers.next().await.unwrap();
17139
17140    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17141        |params, _| async move {
17142            assert_eq!(
17143                params.text_document_position.text_document.uri,
17144                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17145            );
17146            assert_eq!(
17147                params.text_document_position.position,
17148                lsp::Position::new(0, 21),
17149            );
17150
17151            Ok(Some(vec![lsp::TextEdit {
17152                new_text: "]".to_string(),
17153                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17154            }]))
17155        },
17156    );
17157
17158    editor_handle.update_in(cx, |editor, window, cx| {
17159        window.focus(&editor.focus_handle(cx));
17160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17161            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17162        });
17163        editor.handle_input("{", window, cx);
17164    });
17165
17166    cx.executor().run_until_parked();
17167
17168    buffer.update(cx, |buffer, _| {
17169        assert_eq!(
17170            buffer.text(),
17171            "fn main() { let a = {5}; }",
17172            "No extra braces from on type formatting should appear in the buffer"
17173        )
17174    });
17175}
17176
17177#[gpui::test(iterations = 20, seeds(31))]
17178async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17179    init_test(cx, |_| {});
17180
17181    let mut cx = EditorLspTestContext::new_rust(
17182        lsp::ServerCapabilities {
17183            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17184                first_trigger_character: ".".to_string(),
17185                more_trigger_character: None,
17186            }),
17187            ..Default::default()
17188        },
17189        cx,
17190    )
17191    .await;
17192
17193    cx.update_buffer(|buffer, _| {
17194        // This causes autoindent to be async.
17195        buffer.set_sync_parse_timeout(Duration::ZERO)
17196    });
17197
17198    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17199    cx.simulate_keystroke("\n");
17200    cx.run_until_parked();
17201
17202    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17203    let mut request =
17204        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17205            let buffer_cloned = buffer_cloned.clone();
17206            async move {
17207                buffer_cloned.update(&mut cx, |buffer, _| {
17208                    assert_eq!(
17209                        buffer.text(),
17210                        "fn c() {\n    d()\n        .\n}\n",
17211                        "OnTypeFormatting should triggered after autoindent applied"
17212                    )
17213                })?;
17214
17215                Ok(Some(vec![]))
17216            }
17217        });
17218
17219    cx.simulate_keystroke(".");
17220    cx.run_until_parked();
17221
17222    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17223    assert!(request.next().await.is_some());
17224    request.close();
17225    assert!(request.next().await.is_none());
17226}
17227
17228#[gpui::test]
17229async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17230    init_test(cx, |_| {});
17231
17232    let fs = FakeFs::new(cx.executor());
17233    fs.insert_tree(
17234        path!("/a"),
17235        json!({
17236            "main.rs": "fn main() { let a = 5; }",
17237            "other.rs": "// Test file",
17238        }),
17239    )
17240    .await;
17241
17242    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17243
17244    let server_restarts = Arc::new(AtomicUsize::new(0));
17245    let closure_restarts = Arc::clone(&server_restarts);
17246    let language_server_name = "test language server";
17247    let language_name: LanguageName = "Rust".into();
17248
17249    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17250    language_registry.add(Arc::new(Language::new(
17251        LanguageConfig {
17252            name: language_name.clone(),
17253            matcher: LanguageMatcher {
17254                path_suffixes: vec!["rs".to_string()],
17255                ..Default::default()
17256            },
17257            ..Default::default()
17258        },
17259        Some(tree_sitter_rust::LANGUAGE.into()),
17260    )));
17261    let mut fake_servers = language_registry.register_fake_lsp(
17262        "Rust",
17263        FakeLspAdapter {
17264            name: language_server_name,
17265            initialization_options: Some(json!({
17266                "testOptionValue": true
17267            })),
17268            initializer: Some(Box::new(move |fake_server| {
17269                let task_restarts = Arc::clone(&closure_restarts);
17270                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17271                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17272                    futures::future::ready(Ok(()))
17273                });
17274            })),
17275            ..Default::default()
17276        },
17277    );
17278
17279    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17280    let _buffer = project
17281        .update(cx, |project, cx| {
17282            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17283        })
17284        .await
17285        .unwrap();
17286    let _fake_server = fake_servers.next().await.unwrap();
17287    update_test_language_settings(cx, |language_settings| {
17288        language_settings.languages.0.insert(
17289            language_name.clone().0,
17290            LanguageSettingsContent {
17291                tab_size: NonZeroU32::new(8),
17292                ..Default::default()
17293            },
17294        );
17295    });
17296    cx.executor().run_until_parked();
17297    assert_eq!(
17298        server_restarts.load(atomic::Ordering::Acquire),
17299        0,
17300        "Should not restart LSP server on an unrelated change"
17301    );
17302
17303    update_test_project_settings(cx, |project_settings| {
17304        project_settings.lsp.insert(
17305            "Some other server name".into(),
17306            LspSettings {
17307                binary: None,
17308                settings: None,
17309                initialization_options: Some(json!({
17310                    "some other init value": false
17311                })),
17312                enable_lsp_tasks: false,
17313                fetch: None,
17314            },
17315        );
17316    });
17317    cx.executor().run_until_parked();
17318    assert_eq!(
17319        server_restarts.load(atomic::Ordering::Acquire),
17320        0,
17321        "Should not restart LSP server on an unrelated LSP settings change"
17322    );
17323
17324    update_test_project_settings(cx, |project_settings| {
17325        project_settings.lsp.insert(
17326            language_server_name.into(),
17327            LspSettings {
17328                binary: None,
17329                settings: None,
17330                initialization_options: Some(json!({
17331                    "anotherInitValue": false
17332                })),
17333                enable_lsp_tasks: false,
17334                fetch: None,
17335            },
17336        );
17337    });
17338    cx.executor().run_until_parked();
17339    assert_eq!(
17340        server_restarts.load(atomic::Ordering::Acquire),
17341        1,
17342        "Should restart LSP server on a related LSP settings change"
17343    );
17344
17345    update_test_project_settings(cx, |project_settings| {
17346        project_settings.lsp.insert(
17347            language_server_name.into(),
17348            LspSettings {
17349                binary: None,
17350                settings: None,
17351                initialization_options: Some(json!({
17352                    "anotherInitValue": false
17353                })),
17354                enable_lsp_tasks: false,
17355                fetch: None,
17356            },
17357        );
17358    });
17359    cx.executor().run_until_parked();
17360    assert_eq!(
17361        server_restarts.load(atomic::Ordering::Acquire),
17362        1,
17363        "Should not restart LSP server on a related LSP settings change that is the same"
17364    );
17365
17366    update_test_project_settings(cx, |project_settings| {
17367        project_settings.lsp.insert(
17368            language_server_name.into(),
17369            LspSettings {
17370                binary: None,
17371                settings: None,
17372                initialization_options: None,
17373                enable_lsp_tasks: false,
17374                fetch: None,
17375            },
17376        );
17377    });
17378    cx.executor().run_until_parked();
17379    assert_eq!(
17380        server_restarts.load(atomic::Ordering::Acquire),
17381        2,
17382        "Should restart LSP server on another related LSP settings change"
17383    );
17384}
17385
17386#[gpui::test]
17387async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17388    init_test(cx, |_| {});
17389
17390    let mut cx = EditorLspTestContext::new_rust(
17391        lsp::ServerCapabilities {
17392            completion_provider: Some(lsp::CompletionOptions {
17393                trigger_characters: Some(vec![".".to_string()]),
17394                resolve_provider: Some(true),
17395                ..Default::default()
17396            }),
17397            ..Default::default()
17398        },
17399        cx,
17400    )
17401    .await;
17402
17403    cx.set_state("fn main() { let a = 2ˇ; }");
17404    cx.simulate_keystroke(".");
17405    let completion_item = lsp::CompletionItem {
17406        label: "some".into(),
17407        kind: Some(lsp::CompletionItemKind::SNIPPET),
17408        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17409        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17410            kind: lsp::MarkupKind::Markdown,
17411            value: "```rust\nSome(2)\n```".to_string(),
17412        })),
17413        deprecated: Some(false),
17414        sort_text: Some("fffffff2".to_string()),
17415        filter_text: Some("some".to_string()),
17416        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17417        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17418            range: lsp::Range {
17419                start: lsp::Position {
17420                    line: 0,
17421                    character: 22,
17422                },
17423                end: lsp::Position {
17424                    line: 0,
17425                    character: 22,
17426                },
17427            },
17428            new_text: "Some(2)".to_string(),
17429        })),
17430        additional_text_edits: Some(vec![lsp::TextEdit {
17431            range: lsp::Range {
17432                start: lsp::Position {
17433                    line: 0,
17434                    character: 20,
17435                },
17436                end: lsp::Position {
17437                    line: 0,
17438                    character: 22,
17439                },
17440            },
17441            new_text: "".to_string(),
17442        }]),
17443        ..Default::default()
17444    };
17445
17446    let closure_completion_item = completion_item.clone();
17447    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17448        let task_completion_item = closure_completion_item.clone();
17449        async move {
17450            Ok(Some(lsp::CompletionResponse::Array(vec![
17451                task_completion_item,
17452            ])))
17453        }
17454    });
17455
17456    request.next().await;
17457
17458    cx.condition(|editor, _| editor.context_menu_visible())
17459        .await;
17460    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17461        editor
17462            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17463            .unwrap()
17464    });
17465    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17466
17467    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17468        let task_completion_item = completion_item.clone();
17469        async move { Ok(task_completion_item) }
17470    })
17471    .next()
17472    .await
17473    .unwrap();
17474    apply_additional_edits.await.unwrap();
17475    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17476}
17477
17478#[gpui::test]
17479async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17480    init_test(cx, |_| {});
17481
17482    let mut cx = EditorLspTestContext::new_rust(
17483        lsp::ServerCapabilities {
17484            completion_provider: Some(lsp::CompletionOptions {
17485                trigger_characters: Some(vec![".".to_string()]),
17486                resolve_provider: Some(true),
17487                ..Default::default()
17488            }),
17489            ..Default::default()
17490        },
17491        cx,
17492    )
17493    .await;
17494
17495    cx.set_state("fn main() { let a = 2ˇ; }");
17496    cx.simulate_keystroke(".");
17497
17498    let item1 = lsp::CompletionItem {
17499        label: "method id()".to_string(),
17500        filter_text: Some("id".to_string()),
17501        detail: None,
17502        documentation: None,
17503        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17504            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17505            new_text: ".id".to_string(),
17506        })),
17507        ..lsp::CompletionItem::default()
17508    };
17509
17510    let item2 = lsp::CompletionItem {
17511        label: "other".to_string(),
17512        filter_text: Some("other".to_string()),
17513        detail: None,
17514        documentation: None,
17515        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17516            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17517            new_text: ".other".to_string(),
17518        })),
17519        ..lsp::CompletionItem::default()
17520    };
17521
17522    let item1 = item1.clone();
17523    cx.set_request_handler::<lsp::request::Completion, _, _>({
17524        let item1 = item1.clone();
17525        move |_, _, _| {
17526            let item1 = item1.clone();
17527            let item2 = item2.clone();
17528            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17529        }
17530    })
17531    .next()
17532    .await;
17533
17534    cx.condition(|editor, _| editor.context_menu_visible())
17535        .await;
17536    cx.update_editor(|editor, _, _| {
17537        let context_menu = editor.context_menu.borrow_mut();
17538        let context_menu = context_menu
17539            .as_ref()
17540            .expect("Should have the context menu deployed");
17541        match context_menu {
17542            CodeContextMenu::Completions(completions_menu) => {
17543                let completions = completions_menu.completions.borrow_mut();
17544                assert_eq!(
17545                    completions
17546                        .iter()
17547                        .map(|completion| &completion.label.text)
17548                        .collect::<Vec<_>>(),
17549                    vec!["method id()", "other"]
17550                )
17551            }
17552            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17553        }
17554    });
17555
17556    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17557        let item1 = item1.clone();
17558        move |_, item_to_resolve, _| {
17559            let item1 = item1.clone();
17560            async move {
17561                if item1 == item_to_resolve {
17562                    Ok(lsp::CompletionItem {
17563                        label: "method id()".to_string(),
17564                        filter_text: Some("id".to_string()),
17565                        detail: Some("Now resolved!".to_string()),
17566                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17567                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17568                            range: lsp::Range::new(
17569                                lsp::Position::new(0, 22),
17570                                lsp::Position::new(0, 22),
17571                            ),
17572                            new_text: ".id".to_string(),
17573                        })),
17574                        ..lsp::CompletionItem::default()
17575                    })
17576                } else {
17577                    Ok(item_to_resolve)
17578                }
17579            }
17580        }
17581    })
17582    .next()
17583    .await
17584    .unwrap();
17585    cx.run_until_parked();
17586
17587    cx.update_editor(|editor, window, cx| {
17588        editor.context_menu_next(&Default::default(), window, cx);
17589    });
17590
17591    cx.update_editor(|editor, _, _| {
17592        let context_menu = editor.context_menu.borrow_mut();
17593        let context_menu = context_menu
17594            .as_ref()
17595            .expect("Should have the context menu deployed");
17596        match context_menu {
17597            CodeContextMenu::Completions(completions_menu) => {
17598                let completions = completions_menu.completions.borrow_mut();
17599                assert_eq!(
17600                    completions
17601                        .iter()
17602                        .map(|completion| &completion.label.text)
17603                        .collect::<Vec<_>>(),
17604                    vec!["method id() Now resolved!", "other"],
17605                    "Should update first completion label, but not second as the filter text did not match."
17606                );
17607            }
17608            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17609        }
17610    });
17611}
17612
17613#[gpui::test]
17614async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17615    init_test(cx, |_| {});
17616    let mut cx = EditorLspTestContext::new_rust(
17617        lsp::ServerCapabilities {
17618            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17619            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17620            completion_provider: Some(lsp::CompletionOptions {
17621                resolve_provider: Some(true),
17622                ..Default::default()
17623            }),
17624            ..Default::default()
17625        },
17626        cx,
17627    )
17628    .await;
17629    cx.set_state(indoc! {"
17630        struct TestStruct {
17631            field: i32
17632        }
17633
17634        fn mainˇ() {
17635            let unused_var = 42;
17636            let test_struct = TestStruct { field: 42 };
17637        }
17638    "});
17639    let symbol_range = cx.lsp_range(indoc! {"
17640        struct TestStruct {
17641            field: i32
17642        }
17643
17644        «fn main»() {
17645            let unused_var = 42;
17646            let test_struct = TestStruct { field: 42 };
17647        }
17648    "});
17649    let mut hover_requests =
17650        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17651            Ok(Some(lsp::Hover {
17652                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17653                    kind: lsp::MarkupKind::Markdown,
17654                    value: "Function documentation".to_string(),
17655                }),
17656                range: Some(symbol_range),
17657            }))
17658        });
17659
17660    // Case 1: Test that code action menu hide hover popover
17661    cx.dispatch_action(Hover);
17662    hover_requests.next().await;
17663    cx.condition(|editor, _| editor.hover_state.visible()).await;
17664    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17665        move |_, _, _| async move {
17666            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17667                lsp::CodeAction {
17668                    title: "Remove unused variable".to_string(),
17669                    kind: Some(CodeActionKind::QUICKFIX),
17670                    edit: Some(lsp::WorkspaceEdit {
17671                        changes: Some(
17672                            [(
17673                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17674                                vec![lsp::TextEdit {
17675                                    range: lsp::Range::new(
17676                                        lsp::Position::new(5, 4),
17677                                        lsp::Position::new(5, 27),
17678                                    ),
17679                                    new_text: "".to_string(),
17680                                }],
17681                            )]
17682                            .into_iter()
17683                            .collect(),
17684                        ),
17685                        ..Default::default()
17686                    }),
17687                    ..Default::default()
17688                },
17689            )]))
17690        },
17691    );
17692    cx.update_editor(|editor, window, cx| {
17693        editor.toggle_code_actions(
17694            &ToggleCodeActions {
17695                deployed_from: None,
17696                quick_launch: false,
17697            },
17698            window,
17699            cx,
17700        );
17701    });
17702    code_action_requests.next().await;
17703    cx.run_until_parked();
17704    cx.condition(|editor, _| editor.context_menu_visible())
17705        .await;
17706    cx.update_editor(|editor, _, _| {
17707        assert!(
17708            !editor.hover_state.visible(),
17709            "Hover popover should be hidden when code action menu is shown"
17710        );
17711        // Hide code actions
17712        editor.context_menu.take();
17713    });
17714
17715    // Case 2: Test that code completions hide hover popover
17716    cx.dispatch_action(Hover);
17717    hover_requests.next().await;
17718    cx.condition(|editor, _| editor.hover_state.visible()).await;
17719    let counter = Arc::new(AtomicUsize::new(0));
17720    let mut completion_requests =
17721        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17722            let counter = counter.clone();
17723            async move {
17724                counter.fetch_add(1, atomic::Ordering::Release);
17725                Ok(Some(lsp::CompletionResponse::Array(vec![
17726                    lsp::CompletionItem {
17727                        label: "main".into(),
17728                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17729                        detail: Some("() -> ()".to_string()),
17730                        ..Default::default()
17731                    },
17732                    lsp::CompletionItem {
17733                        label: "TestStruct".into(),
17734                        kind: Some(lsp::CompletionItemKind::STRUCT),
17735                        detail: Some("struct TestStruct".to_string()),
17736                        ..Default::default()
17737                    },
17738                ])))
17739            }
17740        });
17741    cx.update_editor(|editor, window, cx| {
17742        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17743    });
17744    completion_requests.next().await;
17745    cx.condition(|editor, _| editor.context_menu_visible())
17746        .await;
17747    cx.update_editor(|editor, _, _| {
17748        assert!(
17749            !editor.hover_state.visible(),
17750            "Hover popover should be hidden when completion menu is shown"
17751        );
17752    });
17753}
17754
17755#[gpui::test]
17756async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17757    init_test(cx, |_| {});
17758
17759    let mut cx = EditorLspTestContext::new_rust(
17760        lsp::ServerCapabilities {
17761            completion_provider: Some(lsp::CompletionOptions {
17762                trigger_characters: Some(vec![".".to_string()]),
17763                resolve_provider: Some(true),
17764                ..Default::default()
17765            }),
17766            ..Default::default()
17767        },
17768        cx,
17769    )
17770    .await;
17771
17772    cx.set_state("fn main() { let a = 2ˇ; }");
17773    cx.simulate_keystroke(".");
17774
17775    let unresolved_item_1 = lsp::CompletionItem {
17776        label: "id".to_string(),
17777        filter_text: Some("id".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: ".id".to_string(),
17783        })),
17784        ..lsp::CompletionItem::default()
17785    };
17786    let resolved_item_1 = 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_1.clone()
17792    };
17793    let unresolved_item_2 = lsp::CompletionItem {
17794        label: "other".to_string(),
17795        filter_text: Some("other".to_string()),
17796        detail: None,
17797        documentation: None,
17798        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17799            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17800            new_text: ".other".to_string(),
17801        })),
17802        ..lsp::CompletionItem::default()
17803    };
17804    let resolved_item_2 = lsp::CompletionItem {
17805        additional_text_edits: Some(vec![lsp::TextEdit {
17806            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17807            new_text: "??".to_string(),
17808        }]),
17809        ..unresolved_item_2.clone()
17810    };
17811
17812    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17813    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17814    cx.lsp
17815        .server
17816        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17817            let unresolved_item_1 = unresolved_item_1.clone();
17818            let resolved_item_1 = resolved_item_1.clone();
17819            let unresolved_item_2 = unresolved_item_2.clone();
17820            let resolved_item_2 = resolved_item_2.clone();
17821            let resolve_requests_1 = resolve_requests_1.clone();
17822            let resolve_requests_2 = resolve_requests_2.clone();
17823            move |unresolved_request, _| {
17824                let unresolved_item_1 = unresolved_item_1.clone();
17825                let resolved_item_1 = resolved_item_1.clone();
17826                let unresolved_item_2 = unresolved_item_2.clone();
17827                let resolved_item_2 = resolved_item_2.clone();
17828                let resolve_requests_1 = resolve_requests_1.clone();
17829                let resolve_requests_2 = resolve_requests_2.clone();
17830                async move {
17831                    if unresolved_request == unresolved_item_1 {
17832                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17833                        Ok(resolved_item_1.clone())
17834                    } else if unresolved_request == unresolved_item_2 {
17835                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17836                        Ok(resolved_item_2.clone())
17837                    } else {
17838                        panic!("Unexpected completion item {unresolved_request:?}")
17839                    }
17840                }
17841            }
17842        })
17843        .detach();
17844
17845    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17846        let unresolved_item_1 = unresolved_item_1.clone();
17847        let unresolved_item_2 = unresolved_item_2.clone();
17848        async move {
17849            Ok(Some(lsp::CompletionResponse::Array(vec![
17850                unresolved_item_1,
17851                unresolved_item_2,
17852            ])))
17853        }
17854    })
17855    .next()
17856    .await;
17857
17858    cx.condition(|editor, _| editor.context_menu_visible())
17859        .await;
17860    cx.update_editor(|editor, _, _| {
17861        let context_menu = editor.context_menu.borrow_mut();
17862        let context_menu = context_menu
17863            .as_ref()
17864            .expect("Should have the context menu deployed");
17865        match context_menu {
17866            CodeContextMenu::Completions(completions_menu) => {
17867                let completions = completions_menu.completions.borrow_mut();
17868                assert_eq!(
17869                    completions
17870                        .iter()
17871                        .map(|completion| &completion.label.text)
17872                        .collect::<Vec<_>>(),
17873                    vec!["id", "other"]
17874                )
17875            }
17876            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17877        }
17878    });
17879    cx.run_until_parked();
17880
17881    cx.update_editor(|editor, window, cx| {
17882        editor.context_menu_next(&ContextMenuNext, window, cx);
17883    });
17884    cx.run_until_parked();
17885    cx.update_editor(|editor, window, cx| {
17886        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17887    });
17888    cx.run_until_parked();
17889    cx.update_editor(|editor, window, cx| {
17890        editor.context_menu_next(&ContextMenuNext, window, cx);
17891    });
17892    cx.run_until_parked();
17893    cx.update_editor(|editor, window, cx| {
17894        editor
17895            .compose_completion(&ComposeCompletion::default(), window, cx)
17896            .expect("No task returned")
17897    })
17898    .await
17899    .expect("Completion failed");
17900    cx.run_until_parked();
17901
17902    cx.update_editor(|editor, _, cx| {
17903        assert_eq!(
17904            resolve_requests_1.load(atomic::Ordering::Acquire),
17905            1,
17906            "Should always resolve once despite multiple selections"
17907        );
17908        assert_eq!(
17909            resolve_requests_2.load(atomic::Ordering::Acquire),
17910            1,
17911            "Should always resolve once after multiple selections and applying the completion"
17912        );
17913        assert_eq!(
17914            editor.text(cx),
17915            "fn main() { let a = ??.other; }",
17916            "Should use resolved data when applying the completion"
17917        );
17918    });
17919}
17920
17921#[gpui::test]
17922async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17923    init_test(cx, |_| {});
17924
17925    let item_0 = lsp::CompletionItem {
17926        label: "abs".into(),
17927        insert_text: Some("abs".into()),
17928        data: Some(json!({ "very": "special"})),
17929        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17930        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17931            lsp::InsertReplaceEdit {
17932                new_text: "abs".to_string(),
17933                insert: lsp::Range::default(),
17934                replace: lsp::Range::default(),
17935            },
17936        )),
17937        ..lsp::CompletionItem::default()
17938    };
17939    let items = iter::once(item_0.clone())
17940        .chain((11..51).map(|i| lsp::CompletionItem {
17941            label: format!("item_{}", i),
17942            insert_text: Some(format!("item_{}", i)),
17943            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17944            ..lsp::CompletionItem::default()
17945        }))
17946        .collect::<Vec<_>>();
17947
17948    let default_commit_characters = vec!["?".to_string()];
17949    let default_data = json!({ "default": "data"});
17950    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17951    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17952    let default_edit_range = lsp::Range {
17953        start: lsp::Position {
17954            line: 0,
17955            character: 5,
17956        },
17957        end: lsp::Position {
17958            line: 0,
17959            character: 5,
17960        },
17961    };
17962
17963    let mut cx = EditorLspTestContext::new_rust(
17964        lsp::ServerCapabilities {
17965            completion_provider: Some(lsp::CompletionOptions {
17966                trigger_characters: Some(vec![".".to_string()]),
17967                resolve_provider: Some(true),
17968                ..Default::default()
17969            }),
17970            ..Default::default()
17971        },
17972        cx,
17973    )
17974    .await;
17975
17976    cx.set_state("fn main() { let a = 2ˇ; }");
17977    cx.simulate_keystroke(".");
17978
17979    let completion_data = default_data.clone();
17980    let completion_characters = default_commit_characters.clone();
17981    let completion_items = items.clone();
17982    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17983        let default_data = completion_data.clone();
17984        let default_commit_characters = completion_characters.clone();
17985        let items = completion_items.clone();
17986        async move {
17987            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17988                items,
17989                item_defaults: Some(lsp::CompletionListItemDefaults {
17990                    data: Some(default_data.clone()),
17991                    commit_characters: Some(default_commit_characters.clone()),
17992                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17993                        default_edit_range,
17994                    )),
17995                    insert_text_format: Some(default_insert_text_format),
17996                    insert_text_mode: Some(default_insert_text_mode),
17997                }),
17998                ..lsp::CompletionList::default()
17999            })))
18000        }
18001    })
18002    .next()
18003    .await;
18004
18005    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18006    cx.lsp
18007        .server
18008        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18009            let closure_resolved_items = resolved_items.clone();
18010            move |item_to_resolve, _| {
18011                let closure_resolved_items = closure_resolved_items.clone();
18012                async move {
18013                    closure_resolved_items.lock().push(item_to_resolve.clone());
18014                    Ok(item_to_resolve)
18015                }
18016            }
18017        })
18018        .detach();
18019
18020    cx.condition(|editor, _| editor.context_menu_visible())
18021        .await;
18022    cx.run_until_parked();
18023    cx.update_editor(|editor, _, _| {
18024        let menu = editor.context_menu.borrow_mut();
18025        match menu.as_ref().expect("should have the completions menu") {
18026            CodeContextMenu::Completions(completions_menu) => {
18027                assert_eq!(
18028                    completions_menu
18029                        .entries
18030                        .borrow()
18031                        .iter()
18032                        .map(|mat| mat.string.clone())
18033                        .collect::<Vec<String>>(),
18034                    items
18035                        .iter()
18036                        .map(|completion| completion.label.clone())
18037                        .collect::<Vec<String>>()
18038                );
18039            }
18040            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18041        }
18042    });
18043    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18044    // with 4 from the end.
18045    assert_eq!(
18046        *resolved_items.lock(),
18047        [&items[0..16], &items[items.len() - 4..items.len()]]
18048            .concat()
18049            .iter()
18050            .cloned()
18051            .map(|mut item| {
18052                if item.data.is_none() {
18053                    item.data = Some(default_data.clone());
18054                }
18055                item
18056            })
18057            .collect::<Vec<lsp::CompletionItem>>(),
18058        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18059    );
18060    resolved_items.lock().clear();
18061
18062    cx.update_editor(|editor, window, cx| {
18063        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18064    });
18065    cx.run_until_parked();
18066    // Completions that have already been resolved are skipped.
18067    assert_eq!(
18068        *resolved_items.lock(),
18069        items[items.len() - 17..items.len() - 4]
18070            .iter()
18071            .cloned()
18072            .map(|mut item| {
18073                if item.data.is_none() {
18074                    item.data = Some(default_data.clone());
18075                }
18076                item
18077            })
18078            .collect::<Vec<lsp::CompletionItem>>()
18079    );
18080    resolved_items.lock().clear();
18081}
18082
18083#[gpui::test]
18084async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18085    init_test(cx, |_| {});
18086
18087    let mut cx = EditorLspTestContext::new(
18088        Language::new(
18089            LanguageConfig {
18090                matcher: LanguageMatcher {
18091                    path_suffixes: vec!["jsx".into()],
18092                    ..Default::default()
18093                },
18094                overrides: [(
18095                    "element".into(),
18096                    LanguageConfigOverride {
18097                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18098                        ..Default::default()
18099                    },
18100                )]
18101                .into_iter()
18102                .collect(),
18103                ..Default::default()
18104            },
18105            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18106        )
18107        .with_override_query("(jsx_self_closing_element) @element")
18108        .unwrap(),
18109        lsp::ServerCapabilities {
18110            completion_provider: Some(lsp::CompletionOptions {
18111                trigger_characters: Some(vec![":".to_string()]),
18112                ..Default::default()
18113            }),
18114            ..Default::default()
18115        },
18116        cx,
18117    )
18118    .await;
18119
18120    cx.lsp
18121        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18122            Ok(Some(lsp::CompletionResponse::Array(vec![
18123                lsp::CompletionItem {
18124                    label: "bg-blue".into(),
18125                    ..Default::default()
18126                },
18127                lsp::CompletionItem {
18128                    label: "bg-red".into(),
18129                    ..Default::default()
18130                },
18131                lsp::CompletionItem {
18132                    label: "bg-yellow".into(),
18133                    ..Default::default()
18134                },
18135            ])))
18136        });
18137
18138    cx.set_state(r#"<p class="bgˇ" />"#);
18139
18140    // Trigger completion when typing a dash, because the dash is an extra
18141    // word character in the 'element' scope, which contains the cursor.
18142    cx.simulate_keystroke("-");
18143    cx.executor().run_until_parked();
18144    cx.update_editor(|editor, _, _| {
18145        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18146        {
18147            assert_eq!(
18148                completion_menu_entries(menu),
18149                &["bg-blue", "bg-red", "bg-yellow"]
18150            );
18151        } else {
18152            panic!("expected completion menu to be open");
18153        }
18154    });
18155
18156    cx.simulate_keystroke("l");
18157    cx.executor().run_until_parked();
18158    cx.update_editor(|editor, _, _| {
18159        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18160        {
18161            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18162        } else {
18163            panic!("expected completion menu to be open");
18164        }
18165    });
18166
18167    // When filtering completions, consider the character after the '-' to
18168    // be the start of a subword.
18169    cx.set_state(r#"<p class="yelˇ" />"#);
18170    cx.simulate_keystroke("l");
18171    cx.executor().run_until_parked();
18172    cx.update_editor(|editor, _, _| {
18173        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18174        {
18175            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18176        } else {
18177            panic!("expected completion menu to be open");
18178        }
18179    });
18180}
18181
18182fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18183    let entries = menu.entries.borrow();
18184    entries.iter().map(|mat| mat.string.clone()).collect()
18185}
18186
18187#[gpui::test]
18188async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18189    init_test(cx, |settings| {
18190        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18191            Formatter::Prettier,
18192        )))
18193    });
18194
18195    let fs = FakeFs::new(cx.executor());
18196    fs.insert_file(path!("/file.ts"), Default::default()).await;
18197
18198    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18199    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18200
18201    language_registry.add(Arc::new(Language::new(
18202        LanguageConfig {
18203            name: "TypeScript".into(),
18204            matcher: LanguageMatcher {
18205                path_suffixes: vec!["ts".to_string()],
18206                ..Default::default()
18207            },
18208            ..Default::default()
18209        },
18210        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18211    )));
18212    update_test_language_settings(cx, |settings| {
18213        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18214    });
18215
18216    let test_plugin = "test_plugin";
18217    let _ = language_registry.register_fake_lsp(
18218        "TypeScript",
18219        FakeLspAdapter {
18220            prettier_plugins: vec![test_plugin],
18221            ..Default::default()
18222        },
18223    );
18224
18225    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18226    let buffer = project
18227        .update(cx, |project, cx| {
18228            project.open_local_buffer(path!("/file.ts"), cx)
18229        })
18230        .await
18231        .unwrap();
18232
18233    let buffer_text = "one\ntwo\nthree\n";
18234    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18235    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18236    editor.update_in(cx, |editor, window, cx| {
18237        editor.set_text(buffer_text, window, cx)
18238    });
18239
18240    editor
18241        .update_in(cx, |editor, window, cx| {
18242            editor.perform_format(
18243                project.clone(),
18244                FormatTrigger::Manual,
18245                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18246                window,
18247                cx,
18248            )
18249        })
18250        .unwrap()
18251        .await;
18252    assert_eq!(
18253        editor.update(cx, |editor, cx| editor.text(cx)),
18254        buffer_text.to_string() + prettier_format_suffix,
18255        "Test prettier formatting was not applied to the original buffer text",
18256    );
18257
18258    update_test_language_settings(cx, |settings| {
18259        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18260    });
18261    let format = editor.update_in(cx, |editor, window, cx| {
18262        editor.perform_format(
18263            project.clone(),
18264            FormatTrigger::Manual,
18265            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18266            window,
18267            cx,
18268        )
18269    });
18270    format.await.unwrap();
18271    assert_eq!(
18272        editor.update(cx, |editor, cx| editor.text(cx)),
18273        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18274        "Autoformatting (via test prettier) was not applied to the original buffer text",
18275    );
18276}
18277
18278#[gpui::test]
18279async fn test_addition_reverts(cx: &mut TestAppContext) {
18280    init_test(cx, |_| {});
18281    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18282    let base_text = indoc! {r#"
18283        struct Row;
18284        struct Row1;
18285        struct Row2;
18286
18287        struct Row4;
18288        struct Row5;
18289        struct Row6;
18290
18291        struct Row8;
18292        struct Row9;
18293        struct Row10;"#};
18294
18295    // When addition hunks are not adjacent to carets, no hunk revert is performed
18296    assert_hunk_revert(
18297        indoc! {r#"struct Row;
18298                   struct Row1;
18299                   struct Row1.1;
18300                   struct Row1.2;
18301                   struct Row2;ˇ
18302
18303                   struct Row4;
18304                   struct Row5;
18305                   struct Row6;
18306
18307                   struct Row8;
18308                   ˇstruct Row9;
18309                   struct Row9.1;
18310                   struct Row9.2;
18311                   struct Row9.3;
18312                   struct Row10;"#},
18313        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18314        indoc! {r#"struct Row;
18315                   struct Row1;
18316                   struct Row1.1;
18317                   struct Row1.2;
18318                   struct Row2;ˇ
18319
18320                   struct Row4;
18321                   struct Row5;
18322                   struct Row6;
18323
18324                   struct Row8;
18325                   ˇstruct Row9;
18326                   struct Row9.1;
18327                   struct Row9.2;
18328                   struct Row9.3;
18329                   struct Row10;"#},
18330        base_text,
18331        &mut cx,
18332    );
18333    // Same for selections
18334    assert_hunk_revert(
18335        indoc! {r#"struct Row;
18336                   struct Row1;
18337                   struct Row2;
18338                   struct Row2.1;
18339                   struct Row2.2;
18340                   «ˇ
18341                   struct Row4;
18342                   struct» Row5;
18343                   «struct Row6;
18344                   ˇ»
18345                   struct Row9.1;
18346                   struct Row9.2;
18347                   struct Row9.3;
18348                   struct Row8;
18349                   struct Row9;
18350                   struct Row10;"#},
18351        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18352        indoc! {r#"struct Row;
18353                   struct Row1;
18354                   struct Row2;
18355                   struct Row2.1;
18356                   struct Row2.2;
18357                   «ˇ
18358                   struct Row4;
18359                   struct» Row5;
18360                   «struct Row6;
18361                   ˇ»
18362                   struct Row9.1;
18363                   struct Row9.2;
18364                   struct Row9.3;
18365                   struct Row8;
18366                   struct Row9;
18367                   struct Row10;"#},
18368        base_text,
18369        &mut cx,
18370    );
18371
18372    // When carets and selections intersect the addition hunks, those are reverted.
18373    // Adjacent carets got merged.
18374    assert_hunk_revert(
18375        indoc! {r#"struct Row;
18376                   ˇ// something on the top
18377                   struct Row1;
18378                   struct Row2;
18379                   struct Roˇw3.1;
18380                   struct Row2.2;
18381                   struct Row2.3;ˇ
18382
18383                   struct Row4;
18384                   struct ˇRow5.1;
18385                   struct Row5.2;
18386                   struct «Rowˇ»5.3;
18387                   struct Row5;
18388                   struct Row6;
18389                   ˇ
18390                   struct Row9.1;
18391                   struct «Rowˇ»9.2;
18392                   struct «ˇRow»9.3;
18393                   struct Row8;
18394                   struct Row9;
18395                   «ˇ// something on bottom»
18396                   struct Row10;"#},
18397        vec![
18398            DiffHunkStatusKind::Added,
18399            DiffHunkStatusKind::Added,
18400            DiffHunkStatusKind::Added,
18401            DiffHunkStatusKind::Added,
18402            DiffHunkStatusKind::Added,
18403        ],
18404        indoc! {r#"struct Row;
18405                   ˇstruct Row1;
18406                   struct Row2;
18407                   ˇ
18408                   struct Row4;
18409                   ˇstruct Row5;
18410                   struct Row6;
18411                   ˇ
18412                   ˇstruct Row8;
18413                   struct Row9;
18414                   ˇstruct Row10;"#},
18415        base_text,
18416        &mut cx,
18417    );
18418}
18419
18420#[gpui::test]
18421async fn test_modification_reverts(cx: &mut TestAppContext) {
18422    init_test(cx, |_| {});
18423    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18424    let base_text = indoc! {r#"
18425        struct Row;
18426        struct Row1;
18427        struct Row2;
18428
18429        struct Row4;
18430        struct Row5;
18431        struct Row6;
18432
18433        struct Row8;
18434        struct Row9;
18435        struct Row10;"#};
18436
18437    // Modification hunks behave the same as the addition ones.
18438    assert_hunk_revert(
18439        indoc! {r#"struct Row;
18440                   struct Row1;
18441                   struct Row33;
18442                   ˇ
18443                   struct Row4;
18444                   struct Row5;
18445                   struct Row6;
18446                   ˇ
18447                   struct Row99;
18448                   struct Row9;
18449                   struct Row10;"#},
18450        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18451        indoc! {r#"struct Row;
18452                   struct Row1;
18453                   struct Row33;
18454                   ˇ
18455                   struct Row4;
18456                   struct Row5;
18457                   struct Row6;
18458                   ˇ
18459                   struct Row99;
18460                   struct Row9;
18461                   struct Row10;"#},
18462        base_text,
18463        &mut cx,
18464    );
18465    assert_hunk_revert(
18466        indoc! {r#"struct Row;
18467                   struct Row1;
18468                   struct Row33;
18469                   «ˇ
18470                   struct Row4;
18471                   struct» Row5;
18472                   «struct Row6;
18473                   ˇ»
18474                   struct Row99;
18475                   struct Row9;
18476                   struct Row10;"#},
18477        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18478        indoc! {r#"struct Row;
18479                   struct Row1;
18480                   struct Row33;
18481                   «ˇ
18482                   struct Row4;
18483                   struct» Row5;
18484                   «struct Row6;
18485                   ˇ»
18486                   struct Row99;
18487                   struct Row9;
18488                   struct Row10;"#},
18489        base_text,
18490        &mut cx,
18491    );
18492
18493    assert_hunk_revert(
18494        indoc! {r#"ˇstruct Row1.1;
18495                   struct Row1;
18496                   «ˇstr»uct Row22;
18497
18498                   struct ˇRow44;
18499                   struct Row5;
18500                   struct «Rˇ»ow66;ˇ
18501
18502                   «struˇ»ct Row88;
18503                   struct Row9;
18504                   struct Row1011;ˇ"#},
18505        vec![
18506            DiffHunkStatusKind::Modified,
18507            DiffHunkStatusKind::Modified,
18508            DiffHunkStatusKind::Modified,
18509            DiffHunkStatusKind::Modified,
18510            DiffHunkStatusKind::Modified,
18511            DiffHunkStatusKind::Modified,
18512        ],
18513        indoc! {r#"struct Row;
18514                   ˇstruct Row1;
18515                   struct Row2;
18516                   ˇ
18517                   struct Row4;
18518                   ˇstruct Row5;
18519                   struct Row6;
18520                   ˇ
18521                   struct Row8;
18522                   ˇstruct Row9;
18523                   struct Row10;ˇ"#},
18524        base_text,
18525        &mut cx,
18526    );
18527}
18528
18529#[gpui::test]
18530async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18531    init_test(cx, |_| {});
18532    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18533    let base_text = indoc! {r#"
18534        one
18535
18536        two
18537        three
18538        "#};
18539
18540    cx.set_head_text(base_text);
18541    cx.set_state("\nˇ\n");
18542    cx.executor().run_until_parked();
18543    cx.update_editor(|editor, _window, cx| {
18544        editor.expand_selected_diff_hunks(cx);
18545    });
18546    cx.executor().run_until_parked();
18547    cx.update_editor(|editor, window, cx| {
18548        editor.backspace(&Default::default(), window, cx);
18549    });
18550    cx.run_until_parked();
18551    cx.assert_state_with_diff(
18552        indoc! {r#"
18553
18554        - two
18555        - threeˇ
18556        +
18557        "#}
18558        .to_string(),
18559    );
18560}
18561
18562#[gpui::test]
18563async fn test_deletion_reverts(cx: &mut TestAppContext) {
18564    init_test(cx, |_| {});
18565    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18566    let base_text = indoc! {r#"struct Row;
18567struct Row1;
18568struct Row2;
18569
18570struct Row4;
18571struct Row5;
18572struct Row6;
18573
18574struct Row8;
18575struct Row9;
18576struct Row10;"#};
18577
18578    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18579    assert_hunk_revert(
18580        indoc! {r#"struct Row;
18581                   struct Row2;
18582
18583                   ˇstruct Row4;
18584                   struct Row5;
18585                   struct Row6;
18586                   ˇ
18587                   struct Row8;
18588                   struct Row10;"#},
18589        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18590        indoc! {r#"struct Row;
18591                   struct Row2;
18592
18593                   ˇstruct Row4;
18594                   struct Row5;
18595                   struct Row6;
18596                   ˇ
18597                   struct Row8;
18598                   struct Row10;"#},
18599        base_text,
18600        &mut cx,
18601    );
18602    assert_hunk_revert(
18603        indoc! {r#"struct Row;
18604                   struct Row2;
18605
18606                   «ˇstruct Row4;
18607                   struct» Row5;
18608                   «struct Row6;
18609                   ˇ»
18610                   struct Row8;
18611                   struct Row10;"#},
18612        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18613        indoc! {r#"struct Row;
18614                   struct Row2;
18615
18616                   «ˇstruct Row4;
18617                   struct» Row5;
18618                   «struct Row6;
18619                   ˇ»
18620                   struct Row8;
18621                   struct Row10;"#},
18622        base_text,
18623        &mut cx,
18624    );
18625
18626    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18627    assert_hunk_revert(
18628        indoc! {r#"struct Row;
18629                   ˇstruct Row2;
18630
18631                   struct Row4;
18632                   struct Row5;
18633                   struct Row6;
18634
18635                   struct Row8;ˇ
18636                   struct Row10;"#},
18637        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18638        indoc! {r#"struct Row;
18639                   struct Row1;
18640                   ˇstruct Row2;
18641
18642                   struct Row4;
18643                   struct Row5;
18644                   struct Row6;
18645
18646                   struct Row8;ˇ
18647                   struct Row9;
18648                   struct Row10;"#},
18649        base_text,
18650        &mut cx,
18651    );
18652    assert_hunk_revert(
18653        indoc! {r#"struct Row;
18654                   struct Row2«ˇ;
18655                   struct Row4;
18656                   struct» Row5;
18657                   «struct Row6;
18658
18659                   struct Row8;ˇ»
18660                   struct Row10;"#},
18661        vec![
18662            DiffHunkStatusKind::Deleted,
18663            DiffHunkStatusKind::Deleted,
18664            DiffHunkStatusKind::Deleted,
18665        ],
18666        indoc! {r#"struct Row;
18667                   struct Row1;
18668                   struct Row2«ˇ;
18669
18670                   struct Row4;
18671                   struct» Row5;
18672                   «struct Row6;
18673
18674                   struct Row8;ˇ»
18675                   struct Row9;
18676                   struct Row10;"#},
18677        base_text,
18678        &mut cx,
18679    );
18680}
18681
18682#[gpui::test]
18683async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18684    init_test(cx, |_| {});
18685
18686    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18687    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18688    let base_text_3 =
18689        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18690
18691    let text_1 = edit_first_char_of_every_line(base_text_1);
18692    let text_2 = edit_first_char_of_every_line(base_text_2);
18693    let text_3 = edit_first_char_of_every_line(base_text_3);
18694
18695    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18696    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18697    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18698
18699    let multibuffer = cx.new(|cx| {
18700        let mut multibuffer = MultiBuffer::new(ReadWrite);
18701        multibuffer.push_excerpts(
18702            buffer_1.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.push_excerpts(
18711            buffer_2.clone(),
18712            [
18713                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18714                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18715                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18716            ],
18717            cx,
18718        );
18719        multibuffer.push_excerpts(
18720            buffer_3.clone(),
18721            [
18722                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18723                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18724                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18725            ],
18726            cx,
18727        );
18728        multibuffer
18729    });
18730
18731    let fs = FakeFs::new(cx.executor());
18732    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18733    let (editor, cx) = cx
18734        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18735    editor.update_in(cx, |editor, _window, cx| {
18736        for (buffer, diff_base) in [
18737            (buffer_1.clone(), base_text_1),
18738            (buffer_2.clone(), base_text_2),
18739            (buffer_3.clone(), base_text_3),
18740        ] {
18741            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18742            editor
18743                .buffer
18744                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18745        }
18746    });
18747    cx.executor().run_until_parked();
18748
18749    editor.update_in(cx, |editor, window, cx| {
18750        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}");
18751        editor.select_all(&SelectAll, window, cx);
18752        editor.git_restore(&Default::default(), window, cx);
18753    });
18754    cx.executor().run_until_parked();
18755
18756    // When all ranges are selected, all buffer hunks are reverted.
18757    editor.update(cx, |editor, cx| {
18758        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");
18759    });
18760    buffer_1.update(cx, |buffer, _| {
18761        assert_eq!(buffer.text(), base_text_1);
18762    });
18763    buffer_2.update(cx, |buffer, _| {
18764        assert_eq!(buffer.text(), base_text_2);
18765    });
18766    buffer_3.update(cx, |buffer, _| {
18767        assert_eq!(buffer.text(), base_text_3);
18768    });
18769
18770    editor.update_in(cx, |editor, window, cx| {
18771        editor.undo(&Default::default(), window, cx);
18772    });
18773
18774    editor.update_in(cx, |editor, window, cx| {
18775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18776            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18777        });
18778        editor.git_restore(&Default::default(), window, cx);
18779    });
18780
18781    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18782    // but not affect buffer_2 and its related excerpts.
18783    editor.update(cx, |editor, cx| {
18784        assert_eq!(
18785            editor.text(cx),
18786            "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}"
18787        );
18788    });
18789    buffer_1.update(cx, |buffer, _| {
18790        assert_eq!(buffer.text(), base_text_1);
18791    });
18792    buffer_2.update(cx, |buffer, _| {
18793        assert_eq!(
18794            buffer.text(),
18795            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18796        );
18797    });
18798    buffer_3.update(cx, |buffer, _| {
18799        assert_eq!(
18800            buffer.text(),
18801            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18802        );
18803    });
18804
18805    fn edit_first_char_of_every_line(text: &str) -> String {
18806        text.split('\n')
18807            .map(|line| format!("X{}", &line[1..]))
18808            .collect::<Vec<_>>()
18809            .join("\n")
18810    }
18811}
18812
18813#[gpui::test]
18814async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18815    init_test(cx, |_| {});
18816
18817    let cols = 4;
18818    let rows = 10;
18819    let sample_text_1 = sample_text(rows, cols, 'a');
18820    assert_eq!(
18821        sample_text_1,
18822        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18823    );
18824    let sample_text_2 = sample_text(rows, cols, 'l');
18825    assert_eq!(
18826        sample_text_2,
18827        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18828    );
18829    let sample_text_3 = sample_text(rows, cols, 'v');
18830    assert_eq!(
18831        sample_text_3,
18832        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18833    );
18834
18835    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18836    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18837    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18838
18839    let multi_buffer = cx.new(|cx| {
18840        let mut multibuffer = MultiBuffer::new(ReadWrite);
18841        multibuffer.push_excerpts(
18842            buffer_1.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.push_excerpts(
18851            buffer_2.clone(),
18852            [
18853                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18854                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18855                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18856            ],
18857            cx,
18858        );
18859        multibuffer.push_excerpts(
18860            buffer_3.clone(),
18861            [
18862                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18863                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18864                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18865            ],
18866            cx,
18867        );
18868        multibuffer
18869    });
18870
18871    let fs = FakeFs::new(cx.executor());
18872    fs.insert_tree(
18873        "/a",
18874        json!({
18875            "main.rs": sample_text_1,
18876            "other.rs": sample_text_2,
18877            "lib.rs": sample_text_3,
18878        }),
18879    )
18880    .await;
18881    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18882    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18883    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18884    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18885        Editor::new(
18886            EditorMode::full(),
18887            multi_buffer,
18888            Some(project.clone()),
18889            window,
18890            cx,
18891        )
18892    });
18893    let multibuffer_item_id = workspace
18894        .update(cx, |workspace, window, cx| {
18895            assert!(
18896                workspace.active_item(cx).is_none(),
18897                "active item should be None before the first item is added"
18898            );
18899            workspace.add_item_to_active_pane(
18900                Box::new(multi_buffer_editor.clone()),
18901                None,
18902                true,
18903                window,
18904                cx,
18905            );
18906            let active_item = workspace
18907                .active_item(cx)
18908                .expect("should have an active item after adding the multi buffer");
18909            assert_eq!(
18910                active_item.buffer_kind(cx),
18911                ItemBufferKind::Multibuffer,
18912                "A multi buffer was expected to active after adding"
18913            );
18914            active_item.item_id()
18915        })
18916        .unwrap();
18917    cx.executor().run_until_parked();
18918
18919    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18920        editor.change_selections(
18921            SelectionEffects::scroll(Autoscroll::Next),
18922            window,
18923            cx,
18924            |s| s.select_ranges(Some(1..2)),
18925        );
18926        editor.open_excerpts(&OpenExcerpts, window, cx);
18927    });
18928    cx.executor().run_until_parked();
18929    let first_item_id = workspace
18930        .update(cx, |workspace, window, cx| {
18931            let active_item = workspace
18932                .active_item(cx)
18933                .expect("should have an active item after navigating into the 1st buffer");
18934            let first_item_id = active_item.item_id();
18935            assert_ne!(
18936                first_item_id, multibuffer_item_id,
18937                "Should navigate into the 1st buffer and activate it"
18938            );
18939            assert_eq!(
18940                active_item.buffer_kind(cx),
18941                ItemBufferKind::Singleton,
18942                "New active item should be a singleton buffer"
18943            );
18944            assert_eq!(
18945                active_item
18946                    .act_as::<Editor>(cx)
18947                    .expect("should have navigated into an editor for the 1st buffer")
18948                    .read(cx)
18949                    .text(cx),
18950                sample_text_1
18951            );
18952
18953            workspace
18954                .go_back(workspace.active_pane().downgrade(), window, cx)
18955                .detach_and_log_err(cx);
18956
18957            first_item_id
18958        })
18959        .unwrap();
18960    cx.executor().run_until_parked();
18961    workspace
18962        .update(cx, |workspace, _, cx| {
18963            let active_item = workspace
18964                .active_item(cx)
18965                .expect("should have an active item after navigating back");
18966            assert_eq!(
18967                active_item.item_id(),
18968                multibuffer_item_id,
18969                "Should navigate back to the multi buffer"
18970            );
18971            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18972        })
18973        .unwrap();
18974
18975    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18976        editor.change_selections(
18977            SelectionEffects::scroll(Autoscroll::Next),
18978            window,
18979            cx,
18980            |s| s.select_ranges(Some(39..40)),
18981        );
18982        editor.open_excerpts(&OpenExcerpts, window, cx);
18983    });
18984    cx.executor().run_until_parked();
18985    let second_item_id = workspace
18986        .update(cx, |workspace, window, cx| {
18987            let active_item = workspace
18988                .active_item(cx)
18989                .expect("should have an active item after navigating into the 2nd buffer");
18990            let second_item_id = active_item.item_id();
18991            assert_ne!(
18992                second_item_id, multibuffer_item_id,
18993                "Should navigate away from the multibuffer"
18994            );
18995            assert_ne!(
18996                second_item_id, first_item_id,
18997                "Should navigate into the 2nd buffer and activate it"
18998            );
18999            assert_eq!(
19000                active_item.buffer_kind(cx),
19001                ItemBufferKind::Singleton,
19002                "New active item should be a singleton buffer"
19003            );
19004            assert_eq!(
19005                active_item
19006                    .act_as::<Editor>(cx)
19007                    .expect("should have navigated into an editor")
19008                    .read(cx)
19009                    .text(cx),
19010                sample_text_2
19011            );
19012
19013            workspace
19014                .go_back(workspace.active_pane().downgrade(), window, cx)
19015                .detach_and_log_err(cx);
19016
19017            second_item_id
19018        })
19019        .unwrap();
19020    cx.executor().run_until_parked();
19021    workspace
19022        .update(cx, |workspace, _, cx| {
19023            let active_item = workspace
19024                .active_item(cx)
19025                .expect("should have an active item after navigating back from the 2nd buffer");
19026            assert_eq!(
19027                active_item.item_id(),
19028                multibuffer_item_id,
19029                "Should navigate back from the 2nd buffer to the multi buffer"
19030            );
19031            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19032        })
19033        .unwrap();
19034
19035    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19036        editor.change_selections(
19037            SelectionEffects::scroll(Autoscroll::Next),
19038            window,
19039            cx,
19040            |s| s.select_ranges(Some(70..70)),
19041        );
19042        editor.open_excerpts(&OpenExcerpts, window, cx);
19043    });
19044    cx.executor().run_until_parked();
19045    workspace
19046        .update(cx, |workspace, window, cx| {
19047            let active_item = workspace
19048                .active_item(cx)
19049                .expect("should have an active item after navigating into the 3rd buffer");
19050            let third_item_id = active_item.item_id();
19051            assert_ne!(
19052                third_item_id, multibuffer_item_id,
19053                "Should navigate into the 3rd buffer and activate it"
19054            );
19055            assert_ne!(third_item_id, first_item_id);
19056            assert_ne!(third_item_id, second_item_id);
19057            assert_eq!(
19058                active_item.buffer_kind(cx),
19059                ItemBufferKind::Singleton,
19060                "New active item should be a singleton buffer"
19061            );
19062            assert_eq!(
19063                active_item
19064                    .act_as::<Editor>(cx)
19065                    .expect("should have navigated into an editor")
19066                    .read(cx)
19067                    .text(cx),
19068                sample_text_3
19069            );
19070
19071            workspace
19072                .go_back(workspace.active_pane().downgrade(), window, cx)
19073                .detach_and_log_err(cx);
19074        })
19075        .unwrap();
19076    cx.executor().run_until_parked();
19077    workspace
19078        .update(cx, |workspace, _, cx| {
19079            let active_item = workspace
19080                .active_item(cx)
19081                .expect("should have an active item after navigating back from the 3rd buffer");
19082            assert_eq!(
19083                active_item.item_id(),
19084                multibuffer_item_id,
19085                "Should navigate back from the 3rd buffer to the multi buffer"
19086            );
19087            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19088        })
19089        .unwrap();
19090}
19091
19092#[gpui::test]
19093async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19094    init_test(cx, |_| {});
19095
19096    let mut cx = EditorTestContext::new(cx).await;
19097
19098    let diff_base = r#"
19099        use some::mod;
19100
19101        const A: u32 = 42;
19102
19103        fn main() {
19104            println!("hello");
19105
19106            println!("world");
19107        }
19108        "#
19109    .unindent();
19110
19111    cx.set_state(
19112        &r#"
19113        use some::modified;
19114
19115        ˇ
19116        fn main() {
19117            println!("hello there");
19118
19119            println!("around the");
19120            println!("world");
19121        }
19122        "#
19123        .unindent(),
19124    );
19125
19126    cx.set_head_text(&diff_base);
19127    executor.run_until_parked();
19128
19129    cx.update_editor(|editor, window, cx| {
19130        editor.go_to_next_hunk(&GoToHunk, window, cx);
19131        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19132    });
19133    executor.run_until_parked();
19134    cx.assert_state_with_diff(
19135        r#"
19136          use some::modified;
19137
19138
19139          fn main() {
19140        -     println!("hello");
19141        + ˇ    println!("hello there");
19142
19143              println!("around the");
19144              println!("world");
19145          }
19146        "#
19147        .unindent(),
19148    );
19149
19150    cx.update_editor(|editor, window, cx| {
19151        for _ in 0..2 {
19152            editor.go_to_next_hunk(&GoToHunk, window, cx);
19153            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19154        }
19155    });
19156    executor.run_until_parked();
19157    cx.assert_state_with_diff(
19158        r#"
19159        - use some::mod;
19160        + ˇuse some::modified;
19161
19162
19163          fn main() {
19164        -     println!("hello");
19165        +     println!("hello there");
19166
19167        +     println!("around the");
19168              println!("world");
19169          }
19170        "#
19171        .unindent(),
19172    );
19173
19174    cx.update_editor(|editor, window, cx| {
19175        editor.go_to_next_hunk(&GoToHunk, window, cx);
19176        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19177    });
19178    executor.run_until_parked();
19179    cx.assert_state_with_diff(
19180        r#"
19181        - use some::mod;
19182        + use some::modified;
19183
19184        - const A: u32 = 42;
19185          ˇ
19186          fn main() {
19187        -     println!("hello");
19188        +     println!("hello there");
19189
19190        +     println!("around the");
19191              println!("world");
19192          }
19193        "#
19194        .unindent(),
19195    );
19196
19197    cx.update_editor(|editor, window, cx| {
19198        editor.cancel(&Cancel, window, cx);
19199    });
19200
19201    cx.assert_state_with_diff(
19202        r#"
19203          use some::modified;
19204
19205          ˇ
19206          fn main() {
19207              println!("hello there");
19208
19209              println!("around the");
19210              println!("world");
19211          }
19212        "#
19213        .unindent(),
19214    );
19215}
19216
19217#[gpui::test]
19218async fn test_diff_base_change_with_expanded_diff_hunks(
19219    executor: BackgroundExecutor,
19220    cx: &mut TestAppContext,
19221) {
19222    init_test(cx, |_| {});
19223
19224    let mut cx = EditorTestContext::new(cx).await;
19225
19226    let diff_base = r#"
19227        use some::mod1;
19228        use some::mod2;
19229
19230        const A: u32 = 42;
19231        const B: u32 = 42;
19232        const C: u32 = 42;
19233
19234        fn main() {
19235            println!("hello");
19236
19237            println!("world");
19238        }
19239        "#
19240    .unindent();
19241
19242    cx.set_state(
19243        &r#"
19244        use some::mod2;
19245
19246        const A: u32 = 42;
19247        const C: u32 = 42;
19248
19249        fn main(ˇ) {
19250            //println!("hello");
19251
19252            println!("world");
19253            //
19254            //
19255        }
19256        "#
19257        .unindent(),
19258    );
19259
19260    cx.set_head_text(&diff_base);
19261    executor.run_until_parked();
19262
19263    cx.update_editor(|editor, window, cx| {
19264        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19265    });
19266    executor.run_until_parked();
19267    cx.assert_state_with_diff(
19268        r#"
19269        - use some::mod1;
19270          use some::mod2;
19271
19272          const A: u32 = 42;
19273        - const B: u32 = 42;
19274          const C: u32 = 42;
19275
19276          fn main(ˇ) {
19277        -     println!("hello");
19278        +     //println!("hello");
19279
19280              println!("world");
19281        +     //
19282        +     //
19283          }
19284        "#
19285        .unindent(),
19286    );
19287
19288    cx.set_head_text("new diff base!");
19289    executor.run_until_parked();
19290    cx.assert_state_with_diff(
19291        r#"
19292        - new diff base!
19293        + use some::mod2;
19294        +
19295        + const A: u32 = 42;
19296        + const C: u32 = 42;
19297        +
19298        + fn main(ˇ) {
19299        +     //println!("hello");
19300        +
19301        +     println!("world");
19302        +     //
19303        +     //
19304        + }
19305        "#
19306        .unindent(),
19307    );
19308}
19309
19310#[gpui::test]
19311async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19312    init_test(cx, |_| {});
19313
19314    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19315    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19316    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19317    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19318    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19319    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19320
19321    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19322    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19323    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19324
19325    let multi_buffer = cx.new(|cx| {
19326        let mut multibuffer = MultiBuffer::new(ReadWrite);
19327        multibuffer.push_excerpts(
19328            buffer_1.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.push_excerpts(
19337            buffer_2.clone(),
19338            [
19339                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19340                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19341                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19342            ],
19343            cx,
19344        );
19345        multibuffer.push_excerpts(
19346            buffer_3.clone(),
19347            [
19348                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19349                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19350                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19351            ],
19352            cx,
19353        );
19354        multibuffer
19355    });
19356
19357    let editor =
19358        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19359    editor
19360        .update(cx, |editor, _window, cx| {
19361            for (buffer, diff_base) in [
19362                (buffer_1.clone(), file_1_old),
19363                (buffer_2.clone(), file_2_old),
19364                (buffer_3.clone(), file_3_old),
19365            ] {
19366                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19367                editor
19368                    .buffer
19369                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19370            }
19371        })
19372        .unwrap();
19373
19374    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19375    cx.run_until_parked();
19376
19377    cx.assert_editor_state(
19378        &"
19379            ˇaaa
19380            ccc
19381            ddd
19382
19383            ggg
19384            hhh
19385
19386
19387            lll
19388            mmm
19389            NNN
19390
19391            qqq
19392            rrr
19393
19394            uuu
19395            111
19396            222
19397            333
19398
19399            666
19400            777
19401
19402            000
19403            !!!"
19404        .unindent(),
19405    );
19406
19407    cx.update_editor(|editor, window, cx| {
19408        editor.select_all(&SelectAll, window, cx);
19409        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19410    });
19411    cx.executor().run_until_parked();
19412
19413    cx.assert_state_with_diff(
19414        "
19415            «aaa
19416          - bbb
19417            ccc
19418            ddd
19419
19420            ggg
19421            hhh
19422
19423
19424            lll
19425            mmm
19426          - nnn
19427          + NNN
19428
19429            qqq
19430            rrr
19431
19432            uuu
19433            111
19434            222
19435            333
19436
19437          + 666
19438            777
19439
19440            000
19441            !!!ˇ»"
19442            .unindent(),
19443    );
19444}
19445
19446#[gpui::test]
19447async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19448    init_test(cx, |_| {});
19449
19450    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19451    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19452
19453    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19454    let multi_buffer = cx.new(|cx| {
19455        let mut multibuffer = MultiBuffer::new(ReadWrite);
19456        multibuffer.push_excerpts(
19457            buffer.clone(),
19458            [
19459                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19460                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19461                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19462            ],
19463            cx,
19464        );
19465        multibuffer
19466    });
19467
19468    let editor =
19469        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19470    editor
19471        .update(cx, |editor, _window, cx| {
19472            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19473            editor
19474                .buffer
19475                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19476        })
19477        .unwrap();
19478
19479    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19480    cx.run_until_parked();
19481
19482    cx.update_editor(|editor, window, cx| {
19483        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19484    });
19485    cx.executor().run_until_parked();
19486
19487    // When the start of a hunk coincides with the start of its excerpt,
19488    // the hunk is expanded. When the start of a hunk is earlier than
19489    // the start of its excerpt, the hunk is not expanded.
19490    cx.assert_state_with_diff(
19491        "
19492            ˇaaa
19493          - bbb
19494          + BBB
19495
19496          - ddd
19497          - eee
19498          + DDD
19499          + EEE
19500            fff
19501
19502            iii
19503        "
19504        .unindent(),
19505    );
19506}
19507
19508#[gpui::test]
19509async fn test_edits_around_expanded_insertion_hunks(
19510    executor: BackgroundExecutor,
19511    cx: &mut TestAppContext,
19512) {
19513    init_test(cx, |_| {});
19514
19515    let mut cx = EditorTestContext::new(cx).await;
19516
19517    let diff_base = r#"
19518        use some::mod1;
19519        use some::mod2;
19520
19521        const A: u32 = 42;
19522
19523        fn main() {
19524            println!("hello");
19525
19526            println!("world");
19527        }
19528        "#
19529    .unindent();
19530    executor.run_until_parked();
19531    cx.set_state(
19532        &r#"
19533        use some::mod1;
19534        use some::mod2;
19535
19536        const A: u32 = 42;
19537        const B: u32 = 42;
19538        const C: u32 = 42;
19539        ˇ
19540
19541        fn main() {
19542            println!("hello");
19543
19544            println!("world");
19545        }
19546        "#
19547        .unindent(),
19548    );
19549
19550    cx.set_head_text(&diff_base);
19551    executor.run_until_parked();
19552
19553    cx.update_editor(|editor, window, cx| {
19554        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19555    });
19556    executor.run_until_parked();
19557
19558    cx.assert_state_with_diff(
19559        r#"
19560        use some::mod1;
19561        use some::mod2;
19562
19563        const A: u32 = 42;
19564      + const B: u32 = 42;
19565      + const C: u32 = 42;
19566      + ˇ
19567
19568        fn main() {
19569            println!("hello");
19570
19571            println!("world");
19572        }
19573      "#
19574        .unindent(),
19575    );
19576
19577    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19578    executor.run_until_parked();
19579
19580    cx.assert_state_with_diff(
19581        r#"
19582        use some::mod1;
19583        use some::mod2;
19584
19585        const A: u32 = 42;
19586      + const B: u32 = 42;
19587      + const C: u32 = 42;
19588      + const D: u32 = 42;
19589      + ˇ
19590
19591        fn main() {
19592            println!("hello");
19593
19594            println!("world");
19595        }
19596      "#
19597        .unindent(),
19598    );
19599
19600    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19601    executor.run_until_parked();
19602
19603    cx.assert_state_with_diff(
19604        r#"
19605        use some::mod1;
19606        use some::mod2;
19607
19608        const A: u32 = 42;
19609      + const B: u32 = 42;
19610      + const C: u32 = 42;
19611      + const D: u32 = 42;
19612      + const E: u32 = 42;
19613      + ˇ
19614
19615        fn main() {
19616            println!("hello");
19617
19618            println!("world");
19619        }
19620      "#
19621        .unindent(),
19622    );
19623
19624    cx.update_editor(|editor, window, cx| {
19625        editor.delete_line(&DeleteLine, window, cx);
19626    });
19627    executor.run_until_parked();
19628
19629    cx.assert_state_with_diff(
19630        r#"
19631        use some::mod1;
19632        use some::mod2;
19633
19634        const A: u32 = 42;
19635      + const B: u32 = 42;
19636      + const C: u32 = 42;
19637      + const D: u32 = 42;
19638      + const E: u32 = 42;
19639        ˇ
19640        fn main() {
19641            println!("hello");
19642
19643            println!("world");
19644        }
19645      "#
19646        .unindent(),
19647    );
19648
19649    cx.update_editor(|editor, window, cx| {
19650        editor.move_up(&MoveUp, window, cx);
19651        editor.delete_line(&DeleteLine, window, cx);
19652        editor.move_up(&MoveUp, window, cx);
19653        editor.delete_line(&DeleteLine, window, cx);
19654        editor.move_up(&MoveUp, window, cx);
19655        editor.delete_line(&DeleteLine, window, cx);
19656    });
19657    executor.run_until_parked();
19658    cx.assert_state_with_diff(
19659        r#"
19660        use some::mod1;
19661        use some::mod2;
19662
19663        const A: u32 = 42;
19664      + const B: u32 = 42;
19665        ˇ
19666        fn main() {
19667            println!("hello");
19668
19669            println!("world");
19670        }
19671      "#
19672        .unindent(),
19673    );
19674
19675    cx.update_editor(|editor, window, cx| {
19676        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19677        editor.delete_line(&DeleteLine, window, cx);
19678    });
19679    executor.run_until_parked();
19680    cx.assert_state_with_diff(
19681        r#"
19682        ˇ
19683        fn main() {
19684            println!("hello");
19685
19686            println!("world");
19687        }
19688      "#
19689        .unindent(),
19690    );
19691}
19692
19693#[gpui::test]
19694async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19695    init_test(cx, |_| {});
19696
19697    let mut cx = EditorTestContext::new(cx).await;
19698    cx.set_head_text(indoc! { "
19699        one
19700        two
19701        three
19702        four
19703        five
19704        "
19705    });
19706    cx.set_state(indoc! { "
19707        one
19708        ˇthree
19709        five
19710    "});
19711    cx.run_until_parked();
19712    cx.update_editor(|editor, window, cx| {
19713        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19714    });
19715    cx.assert_state_with_diff(
19716        indoc! { "
19717        one
19718      - two
19719        ˇthree
19720      - four
19721        five
19722    "}
19723        .to_string(),
19724    );
19725    cx.update_editor(|editor, window, cx| {
19726        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19727    });
19728
19729    cx.assert_state_with_diff(
19730        indoc! { "
19731        one
19732        ˇthree
19733        five
19734    "}
19735        .to_string(),
19736    );
19737
19738    cx.set_state(indoc! { "
19739        one
19740        ˇTWO
19741        three
19742        four
19743        five
19744    "});
19745    cx.run_until_parked();
19746    cx.update_editor(|editor, window, cx| {
19747        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19748    });
19749
19750    cx.assert_state_with_diff(
19751        indoc! { "
19752            one
19753          - two
19754          + ˇTWO
19755            three
19756            four
19757            five
19758        "}
19759        .to_string(),
19760    );
19761    cx.update_editor(|editor, window, cx| {
19762        editor.move_up(&Default::default(), window, cx);
19763        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19764    });
19765    cx.assert_state_with_diff(
19766        indoc! { "
19767            one
19768            ˇTWO
19769            three
19770            four
19771            five
19772        "}
19773        .to_string(),
19774    );
19775}
19776
19777#[gpui::test]
19778async fn test_edits_around_expanded_deletion_hunks(
19779    executor: BackgroundExecutor,
19780    cx: &mut TestAppContext,
19781) {
19782    init_test(cx, |_| {});
19783
19784    let mut cx = EditorTestContext::new(cx).await;
19785
19786    let diff_base = r#"
19787        use some::mod1;
19788        use some::mod2;
19789
19790        const A: u32 = 42;
19791        const B: u32 = 42;
19792        const C: u32 = 42;
19793
19794
19795        fn main() {
19796            println!("hello");
19797
19798            println!("world");
19799        }
19800    "#
19801    .unindent();
19802    executor.run_until_parked();
19803    cx.set_state(
19804        &r#"
19805        use some::mod1;
19806        use some::mod2;
19807
19808        ˇconst B: u32 = 42;
19809        const C: u32 = 42;
19810
19811
19812        fn main() {
19813            println!("hello");
19814
19815            println!("world");
19816        }
19817        "#
19818        .unindent(),
19819    );
19820
19821    cx.set_head_text(&diff_base);
19822    executor.run_until_parked();
19823
19824    cx.update_editor(|editor, window, cx| {
19825        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19826    });
19827    executor.run_until_parked();
19828
19829    cx.assert_state_with_diff(
19830        r#"
19831        use some::mod1;
19832        use some::mod2;
19833
19834      - const A: u32 = 42;
19835        ˇconst B: u32 = 42;
19836        const C: u32 = 42;
19837
19838
19839        fn main() {
19840            println!("hello");
19841
19842            println!("world");
19843        }
19844      "#
19845        .unindent(),
19846    );
19847
19848    cx.update_editor(|editor, window, cx| {
19849        editor.delete_line(&DeleteLine, window, cx);
19850    });
19851    executor.run_until_parked();
19852    cx.assert_state_with_diff(
19853        r#"
19854        use some::mod1;
19855        use some::mod2;
19856
19857      - const A: u32 = 42;
19858      - const B: u32 = 42;
19859        ˇconst C: u32 = 42;
19860
19861
19862        fn main() {
19863            println!("hello");
19864
19865            println!("world");
19866        }
19867      "#
19868        .unindent(),
19869    );
19870
19871    cx.update_editor(|editor, window, cx| {
19872        editor.delete_line(&DeleteLine, window, cx);
19873    });
19874    executor.run_until_parked();
19875    cx.assert_state_with_diff(
19876        r#"
19877        use some::mod1;
19878        use some::mod2;
19879
19880      - const A: u32 = 42;
19881      - const B: u32 = 42;
19882      - const C: u32 = 42;
19883        ˇ
19884
19885        fn main() {
19886            println!("hello");
19887
19888            println!("world");
19889        }
19890      "#
19891        .unindent(),
19892    );
19893
19894    cx.update_editor(|editor, window, cx| {
19895        editor.handle_input("replacement", window, cx);
19896    });
19897    executor.run_until_parked();
19898    cx.assert_state_with_diff(
19899        r#"
19900        use some::mod1;
19901        use some::mod2;
19902
19903      - const A: u32 = 42;
19904      - const B: u32 = 42;
19905      - const C: u32 = 42;
19906      -
19907      + replacementˇ
19908
19909        fn main() {
19910            println!("hello");
19911
19912            println!("world");
19913        }
19914      "#
19915        .unindent(),
19916    );
19917}
19918
19919#[gpui::test]
19920async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19921    init_test(cx, |_| {});
19922
19923    let mut cx = EditorTestContext::new(cx).await;
19924
19925    let base_text = r#"
19926        one
19927        two
19928        three
19929        four
19930        five
19931    "#
19932    .unindent();
19933    executor.run_until_parked();
19934    cx.set_state(
19935        &r#"
19936        one
19937        two
19938        fˇour
19939        five
19940        "#
19941        .unindent(),
19942    );
19943
19944    cx.set_head_text(&base_text);
19945    executor.run_until_parked();
19946
19947    cx.update_editor(|editor, window, cx| {
19948        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19949    });
19950    executor.run_until_parked();
19951
19952    cx.assert_state_with_diff(
19953        r#"
19954          one
19955          two
19956        - three
19957          fˇour
19958          five
19959        "#
19960        .unindent(),
19961    );
19962
19963    cx.update_editor(|editor, window, cx| {
19964        editor.backspace(&Backspace, window, cx);
19965        editor.backspace(&Backspace, window, cx);
19966    });
19967    executor.run_until_parked();
19968    cx.assert_state_with_diff(
19969        r#"
19970          one
19971          two
19972        - threeˇ
19973        - four
19974        + our
19975          five
19976        "#
19977        .unindent(),
19978    );
19979}
19980
19981#[gpui::test]
19982async fn test_edit_after_expanded_modification_hunk(
19983    executor: BackgroundExecutor,
19984    cx: &mut TestAppContext,
19985) {
19986    init_test(cx, |_| {});
19987
19988    let mut cx = EditorTestContext::new(cx).await;
19989
19990    let diff_base = r#"
19991        use some::mod1;
19992        use some::mod2;
19993
19994        const A: u32 = 42;
19995        const B: u32 = 42;
19996        const C: u32 = 42;
19997        const D: u32 = 42;
19998
19999
20000        fn main() {
20001            println!("hello");
20002
20003            println!("world");
20004        }"#
20005    .unindent();
20006
20007    cx.set_state(
20008        &r#"
20009        use some::mod1;
20010        use some::mod2;
20011
20012        const A: u32 = 42;
20013        const B: u32 = 42;
20014        const C: u32 = 43ˇ
20015        const D: u32 = 42;
20016
20017
20018        fn main() {
20019            println!("hello");
20020
20021            println!("world");
20022        }"#
20023        .unindent(),
20024    );
20025
20026    cx.set_head_text(&diff_base);
20027    executor.run_until_parked();
20028    cx.update_editor(|editor, window, cx| {
20029        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20030    });
20031    executor.run_until_parked();
20032
20033    cx.assert_state_with_diff(
20034        r#"
20035        use some::mod1;
20036        use some::mod2;
20037
20038        const A: u32 = 42;
20039        const B: u32 = 42;
20040      - const C: u32 = 42;
20041      + const C: u32 = 43ˇ
20042        const D: u32 = 42;
20043
20044
20045        fn main() {
20046            println!("hello");
20047
20048            println!("world");
20049        }"#
20050        .unindent(),
20051    );
20052
20053    cx.update_editor(|editor, window, cx| {
20054        editor.handle_input("\nnew_line\n", window, cx);
20055    });
20056    executor.run_until_parked();
20057
20058    cx.assert_state_with_diff(
20059        r#"
20060        use some::mod1;
20061        use some::mod2;
20062
20063        const A: u32 = 42;
20064        const B: u32 = 42;
20065      - const C: u32 = 42;
20066      + const C: u32 = 43
20067      + new_line
20068      + ˇ
20069        const D: u32 = 42;
20070
20071
20072        fn main() {
20073            println!("hello");
20074
20075            println!("world");
20076        }"#
20077        .unindent(),
20078    );
20079}
20080
20081#[gpui::test]
20082async fn test_stage_and_unstage_added_file_hunk(
20083    executor: BackgroundExecutor,
20084    cx: &mut TestAppContext,
20085) {
20086    init_test(cx, |_| {});
20087
20088    let mut cx = EditorTestContext::new(cx).await;
20089    cx.update_editor(|editor, _, cx| {
20090        editor.set_expand_all_diff_hunks(cx);
20091    });
20092
20093    let working_copy = r#"
20094            ˇfn main() {
20095                println!("hello, world!");
20096            }
20097        "#
20098    .unindent();
20099
20100    cx.set_state(&working_copy);
20101    executor.run_until_parked();
20102
20103    cx.assert_state_with_diff(
20104        r#"
20105            + ˇfn main() {
20106            +     println!("hello, world!");
20107            + }
20108        "#
20109        .unindent(),
20110    );
20111    cx.assert_index_text(None);
20112
20113    cx.update_editor(|editor, window, cx| {
20114        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20115    });
20116    executor.run_until_parked();
20117    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20118    cx.assert_state_with_diff(
20119        r#"
20120            + ˇfn main() {
20121            +     println!("hello, world!");
20122            + }
20123        "#
20124        .unindent(),
20125    );
20126
20127    cx.update_editor(|editor, window, cx| {
20128        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20129    });
20130    executor.run_until_parked();
20131    cx.assert_index_text(None);
20132}
20133
20134async fn setup_indent_guides_editor(
20135    text: &str,
20136    cx: &mut TestAppContext,
20137) -> (BufferId, EditorTestContext) {
20138    init_test(cx, |_| {});
20139
20140    let mut cx = EditorTestContext::new(cx).await;
20141
20142    let buffer_id = cx.update_editor(|editor, window, cx| {
20143        editor.set_text(text, window, cx);
20144        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20145
20146        buffer_ids[0]
20147    });
20148
20149    (buffer_id, cx)
20150}
20151
20152fn assert_indent_guides(
20153    range: Range<u32>,
20154    expected: Vec<IndentGuide>,
20155    active_indices: Option<Vec<usize>>,
20156    cx: &mut EditorTestContext,
20157) {
20158    let indent_guides = cx.update_editor(|editor, window, cx| {
20159        let snapshot = editor.snapshot(window, cx).display_snapshot;
20160        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20161            editor,
20162            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20163            true,
20164            &snapshot,
20165            cx,
20166        );
20167
20168        indent_guides.sort_by(|a, b| {
20169            a.depth.cmp(&b.depth).then(
20170                a.start_row
20171                    .cmp(&b.start_row)
20172                    .then(a.end_row.cmp(&b.end_row)),
20173            )
20174        });
20175        indent_guides
20176    });
20177
20178    if let Some(expected) = active_indices {
20179        let active_indices = cx.update_editor(|editor, window, cx| {
20180            let snapshot = editor.snapshot(window, cx).display_snapshot;
20181            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20182        });
20183
20184        assert_eq!(
20185            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20186            expected,
20187            "Active indent guide indices do not match"
20188        );
20189    }
20190
20191    assert_eq!(indent_guides, expected, "Indent guides do not match");
20192}
20193
20194fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20195    IndentGuide {
20196        buffer_id,
20197        start_row: MultiBufferRow(start_row),
20198        end_row: MultiBufferRow(end_row),
20199        depth,
20200        tab_size: 4,
20201        settings: IndentGuideSettings {
20202            enabled: true,
20203            line_width: 1,
20204            active_line_width: 1,
20205            coloring: IndentGuideColoring::default(),
20206            background_coloring: IndentGuideBackgroundColoring::default(),
20207        },
20208    }
20209}
20210
20211#[gpui::test]
20212async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20213    let (buffer_id, mut cx) = setup_indent_guides_editor(
20214        &"
20215        fn main() {
20216            let a = 1;
20217        }"
20218        .unindent(),
20219        cx,
20220    )
20221    .await;
20222
20223    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20224}
20225
20226#[gpui::test]
20227async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20228    let (buffer_id, mut cx) = setup_indent_guides_editor(
20229        &"
20230        fn main() {
20231            let a = 1;
20232            let b = 2;
20233        }"
20234        .unindent(),
20235        cx,
20236    )
20237    .await;
20238
20239    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20240}
20241
20242#[gpui::test]
20243async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20244    let (buffer_id, mut cx) = setup_indent_guides_editor(
20245        &"
20246        fn main() {
20247            let a = 1;
20248            if a == 3 {
20249                let b = 2;
20250            } else {
20251                let c = 3;
20252            }
20253        }"
20254        .unindent(),
20255        cx,
20256    )
20257    .await;
20258
20259    assert_indent_guides(
20260        0..8,
20261        vec![
20262            indent_guide(buffer_id, 1, 6, 0),
20263            indent_guide(buffer_id, 3, 3, 1),
20264            indent_guide(buffer_id, 5, 5, 1),
20265        ],
20266        None,
20267        &mut cx,
20268    );
20269}
20270
20271#[gpui::test]
20272async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20273    let (buffer_id, mut cx) = setup_indent_guides_editor(
20274        &"
20275        fn main() {
20276            let a = 1;
20277                let b = 2;
20278            let c = 3;
20279        }"
20280        .unindent(),
20281        cx,
20282    )
20283    .await;
20284
20285    assert_indent_guides(
20286        0..5,
20287        vec![
20288            indent_guide(buffer_id, 1, 3, 0),
20289            indent_guide(buffer_id, 2, 2, 1),
20290        ],
20291        None,
20292        &mut cx,
20293    );
20294}
20295
20296#[gpui::test]
20297async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20298    let (buffer_id, mut cx) = setup_indent_guides_editor(
20299        &"
20300        fn main() {
20301            let a = 1;
20302
20303            let c = 3;
20304        }"
20305        .unindent(),
20306        cx,
20307    )
20308    .await;
20309
20310    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20311}
20312
20313#[gpui::test]
20314async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20315    let (buffer_id, mut cx) = setup_indent_guides_editor(
20316        &"
20317        fn main() {
20318            let a = 1;
20319
20320            let c = 3;
20321
20322            if a == 3 {
20323                let b = 2;
20324            } else {
20325                let c = 3;
20326            }
20327        }"
20328        .unindent(),
20329        cx,
20330    )
20331    .await;
20332
20333    assert_indent_guides(
20334        0..11,
20335        vec![
20336            indent_guide(buffer_id, 1, 9, 0),
20337            indent_guide(buffer_id, 6, 6, 1),
20338            indent_guide(buffer_id, 8, 8, 1),
20339        ],
20340        None,
20341        &mut cx,
20342    );
20343}
20344
20345#[gpui::test]
20346async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20347    let (buffer_id, mut cx) = setup_indent_guides_editor(
20348        &"
20349        fn main() {
20350            let a = 1;
20351
20352            let c = 3;
20353
20354            if a == 3 {
20355                let b = 2;
20356            } else {
20357                let c = 3;
20358            }
20359        }"
20360        .unindent(),
20361        cx,
20362    )
20363    .await;
20364
20365    assert_indent_guides(
20366        1..11,
20367        vec![
20368            indent_guide(buffer_id, 1, 9, 0),
20369            indent_guide(buffer_id, 6, 6, 1),
20370            indent_guide(buffer_id, 8, 8, 1),
20371        ],
20372        None,
20373        &mut cx,
20374    );
20375}
20376
20377#[gpui::test]
20378async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20379    let (buffer_id, mut cx) = setup_indent_guides_editor(
20380        &"
20381        fn main() {
20382            let a = 1;
20383
20384            let c = 3;
20385
20386            if a == 3 {
20387                let b = 2;
20388            } else {
20389                let c = 3;
20390            }
20391        }"
20392        .unindent(),
20393        cx,
20394    )
20395    .await;
20396
20397    assert_indent_guides(
20398        1..10,
20399        vec![
20400            indent_guide(buffer_id, 1, 9, 0),
20401            indent_guide(buffer_id, 6, 6, 1),
20402            indent_guide(buffer_id, 8, 8, 1),
20403        ],
20404        None,
20405        &mut cx,
20406    );
20407}
20408
20409#[gpui::test]
20410async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20411    let (buffer_id, mut cx) = setup_indent_guides_editor(
20412        &"
20413        fn main() {
20414            if a {
20415                b(
20416                    c,
20417                    d,
20418                )
20419            } else {
20420                e(
20421                    f
20422                )
20423            }
20424        }"
20425        .unindent(),
20426        cx,
20427    )
20428    .await;
20429
20430    assert_indent_guides(
20431        0..11,
20432        vec![
20433            indent_guide(buffer_id, 1, 10, 0),
20434            indent_guide(buffer_id, 2, 5, 1),
20435            indent_guide(buffer_id, 7, 9, 1),
20436            indent_guide(buffer_id, 3, 4, 2),
20437            indent_guide(buffer_id, 8, 8, 2),
20438        ],
20439        None,
20440        &mut cx,
20441    );
20442
20443    cx.update_editor(|editor, window, cx| {
20444        editor.fold_at(MultiBufferRow(2), window, cx);
20445        assert_eq!(
20446            editor.display_text(cx),
20447            "
20448            fn main() {
20449                if a {
20450                    b(⋯
20451                    )
20452                } else {
20453                    e(
20454                        f
20455                    )
20456                }
20457            }"
20458            .unindent()
20459        );
20460    });
20461
20462    assert_indent_guides(
20463        0..11,
20464        vec![
20465            indent_guide(buffer_id, 1, 10, 0),
20466            indent_guide(buffer_id, 2, 5, 1),
20467            indent_guide(buffer_id, 7, 9, 1),
20468            indent_guide(buffer_id, 8, 8, 2),
20469        ],
20470        None,
20471        &mut cx,
20472    );
20473}
20474
20475#[gpui::test]
20476async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20477    let (buffer_id, mut cx) = setup_indent_guides_editor(
20478        &"
20479        block1
20480            block2
20481                block3
20482                    block4
20483            block2
20484        block1
20485        block1"
20486            .unindent(),
20487        cx,
20488    )
20489    .await;
20490
20491    assert_indent_guides(
20492        1..10,
20493        vec![
20494            indent_guide(buffer_id, 1, 4, 0),
20495            indent_guide(buffer_id, 2, 3, 1),
20496            indent_guide(buffer_id, 3, 3, 2),
20497        ],
20498        None,
20499        &mut cx,
20500    );
20501}
20502
20503#[gpui::test]
20504async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20505    let (buffer_id, mut cx) = setup_indent_guides_editor(
20506        &"
20507        block1
20508            block2
20509                block3
20510
20511        block1
20512        block1"
20513            .unindent(),
20514        cx,
20515    )
20516    .await;
20517
20518    assert_indent_guides(
20519        0..6,
20520        vec![
20521            indent_guide(buffer_id, 1, 2, 0),
20522            indent_guide(buffer_id, 2, 2, 1),
20523        ],
20524        None,
20525        &mut cx,
20526    );
20527}
20528
20529#[gpui::test]
20530async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20531    let (buffer_id, mut cx) = setup_indent_guides_editor(
20532        &"
20533        function component() {
20534        \treturn (
20535        \t\t\t
20536        \t\t<div>
20537        \t\t\t<abc></abc>
20538        \t\t</div>
20539        \t)
20540        }"
20541        .unindent(),
20542        cx,
20543    )
20544    .await;
20545
20546    assert_indent_guides(
20547        0..8,
20548        vec![
20549            indent_guide(buffer_id, 1, 6, 0),
20550            indent_guide(buffer_id, 2, 5, 1),
20551            indent_guide(buffer_id, 4, 4, 2),
20552        ],
20553        None,
20554        &mut cx,
20555    );
20556}
20557
20558#[gpui::test]
20559async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20560    let (buffer_id, mut cx) = setup_indent_guides_editor(
20561        &"
20562        function component() {
20563        \treturn (
20564        \t
20565        \t\t<div>
20566        \t\t\t<abc></abc>
20567        \t\t</div>
20568        \t)
20569        }"
20570        .unindent(),
20571        cx,
20572    )
20573    .await;
20574
20575    assert_indent_guides(
20576        0..8,
20577        vec![
20578            indent_guide(buffer_id, 1, 6, 0),
20579            indent_guide(buffer_id, 2, 5, 1),
20580            indent_guide(buffer_id, 4, 4, 2),
20581        ],
20582        None,
20583        &mut cx,
20584    );
20585}
20586
20587#[gpui::test]
20588async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20589    let (buffer_id, mut cx) = setup_indent_guides_editor(
20590        &"
20591        block1
20592
20593
20594
20595            block2
20596        "
20597        .unindent(),
20598        cx,
20599    )
20600    .await;
20601
20602    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20603}
20604
20605#[gpui::test]
20606async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20607    let (buffer_id, mut cx) = setup_indent_guides_editor(
20608        &"
20609        def a:
20610        \tb = 3
20611        \tif True:
20612        \t\tc = 4
20613        \t\td = 5
20614        \tprint(b)
20615        "
20616        .unindent(),
20617        cx,
20618    )
20619    .await;
20620
20621    assert_indent_guides(
20622        0..6,
20623        vec![
20624            indent_guide(buffer_id, 1, 5, 0),
20625            indent_guide(buffer_id, 3, 4, 1),
20626        ],
20627        None,
20628        &mut cx,
20629    );
20630}
20631
20632#[gpui::test]
20633async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20634    let (buffer_id, mut cx) = setup_indent_guides_editor(
20635        &"
20636    fn main() {
20637        let a = 1;
20638    }"
20639        .unindent(),
20640        cx,
20641    )
20642    .await;
20643
20644    cx.update_editor(|editor, window, cx| {
20645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20646            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20647        });
20648    });
20649
20650    assert_indent_guides(
20651        0..3,
20652        vec![indent_guide(buffer_id, 1, 1, 0)],
20653        Some(vec![0]),
20654        &mut cx,
20655    );
20656}
20657
20658#[gpui::test]
20659async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20660    let (buffer_id, mut cx) = setup_indent_guides_editor(
20661        &"
20662    fn main() {
20663        if 1 == 2 {
20664            let a = 1;
20665        }
20666    }"
20667        .unindent(),
20668        cx,
20669    )
20670    .await;
20671
20672    cx.update_editor(|editor, window, cx| {
20673        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20674            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20675        });
20676    });
20677
20678    assert_indent_guides(
20679        0..4,
20680        vec![
20681            indent_guide(buffer_id, 1, 3, 0),
20682            indent_guide(buffer_id, 2, 2, 1),
20683        ],
20684        Some(vec![1]),
20685        &mut cx,
20686    );
20687
20688    cx.update_editor(|editor, window, cx| {
20689        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20690            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20691        });
20692    });
20693
20694    assert_indent_guides(
20695        0..4,
20696        vec![
20697            indent_guide(buffer_id, 1, 3, 0),
20698            indent_guide(buffer_id, 2, 2, 1),
20699        ],
20700        Some(vec![1]),
20701        &mut cx,
20702    );
20703
20704    cx.update_editor(|editor, window, cx| {
20705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20706            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20707        });
20708    });
20709
20710    assert_indent_guides(
20711        0..4,
20712        vec![
20713            indent_guide(buffer_id, 1, 3, 0),
20714            indent_guide(buffer_id, 2, 2, 1),
20715        ],
20716        Some(vec![0]),
20717        &mut cx,
20718    );
20719}
20720
20721#[gpui::test]
20722async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20723    let (buffer_id, mut cx) = setup_indent_guides_editor(
20724        &"
20725    fn main() {
20726        let a = 1;
20727
20728        let b = 2;
20729    }"
20730        .unindent(),
20731        cx,
20732    )
20733    .await;
20734
20735    cx.update_editor(|editor, window, cx| {
20736        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20737            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20738        });
20739    });
20740
20741    assert_indent_guides(
20742        0..5,
20743        vec![indent_guide(buffer_id, 1, 3, 0)],
20744        Some(vec![0]),
20745        &mut cx,
20746    );
20747}
20748
20749#[gpui::test]
20750async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20751    let (buffer_id, mut cx) = setup_indent_guides_editor(
20752        &"
20753    def m:
20754        a = 1
20755        pass"
20756            .unindent(),
20757        cx,
20758    )
20759    .await;
20760
20761    cx.update_editor(|editor, window, cx| {
20762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20763            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20764        });
20765    });
20766
20767    assert_indent_guides(
20768        0..3,
20769        vec![indent_guide(buffer_id, 1, 2, 0)],
20770        Some(vec![0]),
20771        &mut cx,
20772    );
20773}
20774
20775#[gpui::test]
20776async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20777    init_test(cx, |_| {});
20778    let mut cx = EditorTestContext::new(cx).await;
20779    let text = indoc! {
20780        "
20781        impl A {
20782            fn b() {
20783                0;
20784                3;
20785                5;
20786                6;
20787                7;
20788            }
20789        }
20790        "
20791    };
20792    let base_text = indoc! {
20793        "
20794        impl A {
20795            fn b() {
20796                0;
20797                1;
20798                2;
20799                3;
20800                4;
20801            }
20802            fn c() {
20803                5;
20804                6;
20805                7;
20806            }
20807        }
20808        "
20809    };
20810
20811    cx.update_editor(|editor, window, cx| {
20812        editor.set_text(text, window, cx);
20813
20814        editor.buffer().update(cx, |multibuffer, cx| {
20815            let buffer = multibuffer.as_singleton().unwrap();
20816            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20817
20818            multibuffer.set_all_diff_hunks_expanded(cx);
20819            multibuffer.add_diff(diff, cx);
20820
20821            buffer.read(cx).remote_id()
20822        })
20823    });
20824    cx.run_until_parked();
20825
20826    cx.assert_state_with_diff(
20827        indoc! { "
20828          impl A {
20829              fn b() {
20830                  0;
20831        -         1;
20832        -         2;
20833                  3;
20834        -         4;
20835        -     }
20836        -     fn c() {
20837                  5;
20838                  6;
20839                  7;
20840              }
20841          }
20842          ˇ"
20843        }
20844        .to_string(),
20845    );
20846
20847    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20848        editor
20849            .snapshot(window, cx)
20850            .buffer_snapshot()
20851            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20852            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20853            .collect::<Vec<_>>()
20854    });
20855    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20856    assert_eq!(
20857        actual_guides,
20858        vec![
20859            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20860            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20861            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20862        ]
20863    );
20864}
20865
20866#[gpui::test]
20867async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20868    init_test(cx, |_| {});
20869    let mut cx = EditorTestContext::new(cx).await;
20870
20871    let diff_base = r#"
20872        a
20873        b
20874        c
20875        "#
20876    .unindent();
20877
20878    cx.set_state(
20879        &r#"
20880        ˇA
20881        b
20882        C
20883        "#
20884        .unindent(),
20885    );
20886    cx.set_head_text(&diff_base);
20887    cx.update_editor(|editor, window, cx| {
20888        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20889    });
20890    executor.run_until_parked();
20891
20892    let both_hunks_expanded = r#"
20893        - a
20894        + ˇA
20895          b
20896        - c
20897        + C
20898        "#
20899    .unindent();
20900
20901    cx.assert_state_with_diff(both_hunks_expanded.clone());
20902
20903    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20904        let snapshot = editor.snapshot(window, cx);
20905        let hunks = editor
20906            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20907            .collect::<Vec<_>>();
20908        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20909        let buffer_id = hunks[0].buffer_id;
20910        hunks
20911            .into_iter()
20912            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20913            .collect::<Vec<_>>()
20914    });
20915    assert_eq!(hunk_ranges.len(), 2);
20916
20917    cx.update_editor(|editor, _, cx| {
20918        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20919    });
20920    executor.run_until_parked();
20921
20922    let second_hunk_expanded = r#"
20923          ˇA
20924          b
20925        - c
20926        + C
20927        "#
20928    .unindent();
20929
20930    cx.assert_state_with_diff(second_hunk_expanded);
20931
20932    cx.update_editor(|editor, _, cx| {
20933        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20934    });
20935    executor.run_until_parked();
20936
20937    cx.assert_state_with_diff(both_hunks_expanded.clone());
20938
20939    cx.update_editor(|editor, _, cx| {
20940        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20941    });
20942    executor.run_until_parked();
20943
20944    let first_hunk_expanded = r#"
20945        - a
20946        + ˇA
20947          b
20948          C
20949        "#
20950    .unindent();
20951
20952    cx.assert_state_with_diff(first_hunk_expanded);
20953
20954    cx.update_editor(|editor, _, cx| {
20955        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20956    });
20957    executor.run_until_parked();
20958
20959    cx.assert_state_with_diff(both_hunks_expanded);
20960
20961    cx.set_state(
20962        &r#"
20963        ˇA
20964        b
20965        "#
20966        .unindent(),
20967    );
20968    cx.run_until_parked();
20969
20970    // TODO this cursor position seems bad
20971    cx.assert_state_with_diff(
20972        r#"
20973        - ˇa
20974        + A
20975          b
20976        "#
20977        .unindent(),
20978    );
20979
20980    cx.update_editor(|editor, window, cx| {
20981        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20982    });
20983
20984    cx.assert_state_with_diff(
20985        r#"
20986            - ˇa
20987            + A
20988              b
20989            - c
20990            "#
20991        .unindent(),
20992    );
20993
20994    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20995        let snapshot = editor.snapshot(window, cx);
20996        let hunks = editor
20997            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20998            .collect::<Vec<_>>();
20999        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21000        let buffer_id = hunks[0].buffer_id;
21001        hunks
21002            .into_iter()
21003            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21004            .collect::<Vec<_>>()
21005    });
21006    assert_eq!(hunk_ranges.len(), 2);
21007
21008    cx.update_editor(|editor, _, cx| {
21009        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21010    });
21011    executor.run_until_parked();
21012
21013    cx.assert_state_with_diff(
21014        r#"
21015        - ˇa
21016        + A
21017          b
21018        "#
21019        .unindent(),
21020    );
21021}
21022
21023#[gpui::test]
21024async fn test_toggle_deletion_hunk_at_start_of_file(
21025    executor: BackgroundExecutor,
21026    cx: &mut TestAppContext,
21027) {
21028    init_test(cx, |_| {});
21029    let mut cx = EditorTestContext::new(cx).await;
21030
21031    let diff_base = r#"
21032        a
21033        b
21034        c
21035        "#
21036    .unindent();
21037
21038    cx.set_state(
21039        &r#"
21040        ˇb
21041        c
21042        "#
21043        .unindent(),
21044    );
21045    cx.set_head_text(&diff_base);
21046    cx.update_editor(|editor, window, cx| {
21047        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21048    });
21049    executor.run_until_parked();
21050
21051    let hunk_expanded = r#"
21052        - a
21053          ˇb
21054          c
21055        "#
21056    .unindent();
21057
21058    cx.assert_state_with_diff(hunk_expanded.clone());
21059
21060    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21061        let snapshot = editor.snapshot(window, cx);
21062        let hunks = editor
21063            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21064            .collect::<Vec<_>>();
21065        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21066        let buffer_id = hunks[0].buffer_id;
21067        hunks
21068            .into_iter()
21069            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21070            .collect::<Vec<_>>()
21071    });
21072    assert_eq!(hunk_ranges.len(), 1);
21073
21074    cx.update_editor(|editor, _, cx| {
21075        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21076    });
21077    executor.run_until_parked();
21078
21079    let hunk_collapsed = r#"
21080          ˇb
21081          c
21082        "#
21083    .unindent();
21084
21085    cx.assert_state_with_diff(hunk_collapsed);
21086
21087    cx.update_editor(|editor, _, cx| {
21088        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21089    });
21090    executor.run_until_parked();
21091
21092    cx.assert_state_with_diff(hunk_expanded);
21093}
21094
21095#[gpui::test]
21096async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21097    init_test(cx, |_| {});
21098
21099    let fs = FakeFs::new(cx.executor());
21100    fs.insert_tree(
21101        path!("/test"),
21102        json!({
21103            ".git": {},
21104            "file-1": "ONE\n",
21105            "file-2": "TWO\n",
21106            "file-3": "THREE\n",
21107        }),
21108    )
21109    .await;
21110
21111    fs.set_head_for_repo(
21112        path!("/test/.git").as_ref(),
21113        &[
21114            ("file-1", "one\n".into()),
21115            ("file-2", "two\n".into()),
21116            ("file-3", "three\n".into()),
21117        ],
21118        "deadbeef",
21119    );
21120
21121    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21122    let mut buffers = vec![];
21123    for i in 1..=3 {
21124        let buffer = project
21125            .update(cx, |project, cx| {
21126                let path = format!(path!("/test/file-{}"), i);
21127                project.open_local_buffer(path, cx)
21128            })
21129            .await
21130            .unwrap();
21131        buffers.push(buffer);
21132    }
21133
21134    let multibuffer = cx.new(|cx| {
21135        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21136        multibuffer.set_all_diff_hunks_expanded(cx);
21137        for buffer in &buffers {
21138            let snapshot = buffer.read(cx).snapshot();
21139            multibuffer.set_excerpts_for_path(
21140                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21141                buffer.clone(),
21142                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21143                2,
21144                cx,
21145            );
21146        }
21147        multibuffer
21148    });
21149
21150    let editor = cx.add_window(|window, cx| {
21151        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21152    });
21153    cx.run_until_parked();
21154
21155    let snapshot = editor
21156        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21157        .unwrap();
21158    let hunks = snapshot
21159        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21160        .map(|hunk| match hunk {
21161            DisplayDiffHunk::Unfolded {
21162                display_row_range, ..
21163            } => display_row_range,
21164            DisplayDiffHunk::Folded { .. } => unreachable!(),
21165        })
21166        .collect::<Vec<_>>();
21167    assert_eq!(
21168        hunks,
21169        [
21170            DisplayRow(2)..DisplayRow(4),
21171            DisplayRow(7)..DisplayRow(9),
21172            DisplayRow(12)..DisplayRow(14),
21173        ]
21174    );
21175}
21176
21177#[gpui::test]
21178async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21179    init_test(cx, |_| {});
21180
21181    let mut cx = EditorTestContext::new(cx).await;
21182    cx.set_head_text(indoc! { "
21183        one
21184        two
21185        three
21186        four
21187        five
21188        "
21189    });
21190    cx.set_index_text(indoc! { "
21191        one
21192        two
21193        three
21194        four
21195        five
21196        "
21197    });
21198    cx.set_state(indoc! {"
21199        one
21200        TWO
21201        ˇTHREE
21202        FOUR
21203        five
21204    "});
21205    cx.run_until_parked();
21206    cx.update_editor(|editor, window, cx| {
21207        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21208    });
21209    cx.run_until_parked();
21210    cx.assert_index_text(Some(indoc! {"
21211        one
21212        TWO
21213        THREE
21214        FOUR
21215        five
21216    "}));
21217    cx.set_state(indoc! { "
21218        one
21219        TWO
21220        ˇTHREE-HUNDRED
21221        FOUR
21222        five
21223    "});
21224    cx.run_until_parked();
21225    cx.update_editor(|editor, window, cx| {
21226        let snapshot = editor.snapshot(window, cx);
21227        let hunks = editor
21228            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21229            .collect::<Vec<_>>();
21230        assert_eq!(hunks.len(), 1);
21231        assert_eq!(
21232            hunks[0].status(),
21233            DiffHunkStatus {
21234                kind: DiffHunkStatusKind::Modified,
21235                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21236            }
21237        );
21238
21239        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21240    });
21241    cx.run_until_parked();
21242    cx.assert_index_text(Some(indoc! {"
21243        one
21244        TWO
21245        THREE-HUNDRED
21246        FOUR
21247        five
21248    "}));
21249}
21250
21251#[gpui::test]
21252fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21253    init_test(cx, |_| {});
21254
21255    let editor = cx.add_window(|window, cx| {
21256        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21257        build_editor(buffer, window, cx)
21258    });
21259
21260    let render_args = Arc::new(Mutex::new(None));
21261    let snapshot = editor
21262        .update(cx, |editor, window, cx| {
21263            let snapshot = editor.buffer().read(cx).snapshot(cx);
21264            let range =
21265                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21266
21267            struct RenderArgs {
21268                row: MultiBufferRow,
21269                folded: bool,
21270                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21271            }
21272
21273            let crease = Crease::inline(
21274                range,
21275                FoldPlaceholder::test(),
21276                {
21277                    let toggle_callback = render_args.clone();
21278                    move |row, folded, callback, _window, _cx| {
21279                        *toggle_callback.lock() = Some(RenderArgs {
21280                            row,
21281                            folded,
21282                            callback,
21283                        });
21284                        div()
21285                    }
21286                },
21287                |_row, _folded, _window, _cx| div(),
21288            );
21289
21290            editor.insert_creases(Some(crease), cx);
21291            let snapshot = editor.snapshot(window, cx);
21292            let _div =
21293                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21294            snapshot
21295        })
21296        .unwrap();
21297
21298    let render_args = render_args.lock().take().unwrap();
21299    assert_eq!(render_args.row, MultiBufferRow(1));
21300    assert!(!render_args.folded);
21301    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21302
21303    cx.update_window(*editor, |_, window, cx| {
21304        (render_args.callback)(true, window, cx)
21305    })
21306    .unwrap();
21307    let snapshot = editor
21308        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21309        .unwrap();
21310    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21311
21312    cx.update_window(*editor, |_, window, cx| {
21313        (render_args.callback)(false, window, cx)
21314    })
21315    .unwrap();
21316    let snapshot = editor
21317        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21318        .unwrap();
21319    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21320}
21321
21322#[gpui::test]
21323async fn test_input_text(cx: &mut TestAppContext) {
21324    init_test(cx, |_| {});
21325    let mut cx = EditorTestContext::new(cx).await;
21326
21327    cx.set_state(
21328        &r#"ˇone
21329        two
21330
21331        three
21332        fourˇ
21333        five
21334
21335        siˇx"#
21336            .unindent(),
21337    );
21338
21339    cx.dispatch_action(HandleInput(String::new()));
21340    cx.assert_editor_state(
21341        &r#"ˇone
21342        two
21343
21344        three
21345        fourˇ
21346        five
21347
21348        siˇx"#
21349            .unindent(),
21350    );
21351
21352    cx.dispatch_action(HandleInput("AAAA".to_string()));
21353    cx.assert_editor_state(
21354        &r#"AAAAˇone
21355        two
21356
21357        three
21358        fourAAAAˇ
21359        five
21360
21361        siAAAAˇx"#
21362            .unindent(),
21363    );
21364}
21365
21366#[gpui::test]
21367async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21368    init_test(cx, |_| {});
21369
21370    let mut cx = EditorTestContext::new(cx).await;
21371    cx.set_state(
21372        r#"let foo = 1;
21373let foo = 2;
21374let foo = 3;
21375let fooˇ = 4;
21376let foo = 5;
21377let foo = 6;
21378let foo = 7;
21379let foo = 8;
21380let foo = 9;
21381let foo = 10;
21382let foo = 11;
21383let foo = 12;
21384let foo = 13;
21385let foo = 14;
21386let foo = 15;"#,
21387    );
21388
21389    cx.update_editor(|e, window, cx| {
21390        assert_eq!(
21391            e.next_scroll_position,
21392            NextScrollCursorCenterTopBottom::Center,
21393            "Default next scroll direction is center",
21394        );
21395
21396        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21397        assert_eq!(
21398            e.next_scroll_position,
21399            NextScrollCursorCenterTopBottom::Top,
21400            "After center, next scroll direction should be top",
21401        );
21402
21403        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21404        assert_eq!(
21405            e.next_scroll_position,
21406            NextScrollCursorCenterTopBottom::Bottom,
21407            "After top, next scroll direction should be bottom",
21408        );
21409
21410        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21411        assert_eq!(
21412            e.next_scroll_position,
21413            NextScrollCursorCenterTopBottom::Center,
21414            "After bottom, scrolling should start over",
21415        );
21416
21417        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21418        assert_eq!(
21419            e.next_scroll_position,
21420            NextScrollCursorCenterTopBottom::Top,
21421            "Scrolling continues if retriggered fast enough"
21422        );
21423    });
21424
21425    cx.executor()
21426        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21427    cx.executor().run_until_parked();
21428    cx.update_editor(|e, _, _| {
21429        assert_eq!(
21430            e.next_scroll_position,
21431            NextScrollCursorCenterTopBottom::Center,
21432            "If scrolling is not triggered fast enough, it should reset"
21433        );
21434    });
21435}
21436
21437#[gpui::test]
21438async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21439    init_test(cx, |_| {});
21440    let mut cx = EditorLspTestContext::new_rust(
21441        lsp::ServerCapabilities {
21442            definition_provider: Some(lsp::OneOf::Left(true)),
21443            references_provider: Some(lsp::OneOf::Left(true)),
21444            ..lsp::ServerCapabilities::default()
21445        },
21446        cx,
21447    )
21448    .await;
21449
21450    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21451        let go_to_definition = cx
21452            .lsp
21453            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21454                move |params, _| async move {
21455                    if empty_go_to_definition {
21456                        Ok(None)
21457                    } else {
21458                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21459                            uri: params.text_document_position_params.text_document.uri,
21460                            range: lsp::Range::new(
21461                                lsp::Position::new(4, 3),
21462                                lsp::Position::new(4, 6),
21463                            ),
21464                        })))
21465                    }
21466                },
21467            );
21468        let references = cx
21469            .lsp
21470            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21471                Ok(Some(vec![lsp::Location {
21472                    uri: params.text_document_position.text_document.uri,
21473                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21474                }]))
21475            });
21476        (go_to_definition, references)
21477    };
21478
21479    cx.set_state(
21480        &r#"fn one() {
21481            let mut a = ˇtwo();
21482        }
21483
21484        fn two() {}"#
21485            .unindent(),
21486    );
21487    set_up_lsp_handlers(false, &mut cx);
21488    let navigated = cx
21489        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21490        .await
21491        .expect("Failed to navigate to definition");
21492    assert_eq!(
21493        navigated,
21494        Navigated::Yes,
21495        "Should have navigated to definition from the GetDefinition response"
21496    );
21497    cx.assert_editor_state(
21498        &r#"fn one() {
21499            let mut a = two();
21500        }
21501
21502        fn «twoˇ»() {}"#
21503            .unindent(),
21504    );
21505
21506    let editors = cx.update_workspace(|workspace, _, cx| {
21507        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21508    });
21509    cx.update_editor(|_, _, test_editor_cx| {
21510        assert_eq!(
21511            editors.len(),
21512            1,
21513            "Initially, only one, test, editor should be open in the workspace"
21514        );
21515        assert_eq!(
21516            test_editor_cx.entity(),
21517            editors.last().expect("Asserted len is 1").clone()
21518        );
21519    });
21520
21521    set_up_lsp_handlers(true, &mut cx);
21522    let navigated = cx
21523        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21524        .await
21525        .expect("Failed to navigate to lookup references");
21526    assert_eq!(
21527        navigated,
21528        Navigated::Yes,
21529        "Should have navigated to references as a fallback after empty GoToDefinition response"
21530    );
21531    // We should not change the selections in the existing file,
21532    // if opening another milti buffer with the references
21533    cx.assert_editor_state(
21534        &r#"fn one() {
21535            let mut a = two();
21536        }
21537
21538        fn «twoˇ»() {}"#
21539            .unindent(),
21540    );
21541    let editors = cx.update_workspace(|workspace, _, cx| {
21542        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21543    });
21544    cx.update_editor(|_, _, test_editor_cx| {
21545        assert_eq!(
21546            editors.len(),
21547            2,
21548            "After falling back to references search, we open a new editor with the results"
21549        );
21550        let references_fallback_text = editors
21551            .into_iter()
21552            .find(|new_editor| *new_editor != test_editor_cx.entity())
21553            .expect("Should have one non-test editor now")
21554            .read(test_editor_cx)
21555            .text(test_editor_cx);
21556        assert_eq!(
21557            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21558            "Should use the range from the references response and not the GoToDefinition one"
21559        );
21560    });
21561}
21562
21563#[gpui::test]
21564async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21565    init_test(cx, |_| {});
21566    cx.update(|cx| {
21567        let mut editor_settings = EditorSettings::get_global(cx).clone();
21568        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21569        EditorSettings::override_global(editor_settings, cx);
21570    });
21571    let mut cx = EditorLspTestContext::new_rust(
21572        lsp::ServerCapabilities {
21573            definition_provider: Some(lsp::OneOf::Left(true)),
21574            references_provider: Some(lsp::OneOf::Left(true)),
21575            ..lsp::ServerCapabilities::default()
21576        },
21577        cx,
21578    )
21579    .await;
21580    let original_state = r#"fn one() {
21581        let mut a = ˇtwo();
21582    }
21583
21584    fn two() {}"#
21585        .unindent();
21586    cx.set_state(&original_state);
21587
21588    let mut go_to_definition = cx
21589        .lsp
21590        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21591            move |_, _| async move { Ok(None) },
21592        );
21593    let _references = cx
21594        .lsp
21595        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21596            panic!("Should not call for references with no go to definition fallback")
21597        });
21598
21599    let navigated = cx
21600        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21601        .await
21602        .expect("Failed to navigate to lookup references");
21603    go_to_definition
21604        .next()
21605        .await
21606        .expect("Should have called the go_to_definition handler");
21607
21608    assert_eq!(
21609        navigated,
21610        Navigated::No,
21611        "Should have navigated to references as a fallback after empty GoToDefinition response"
21612    );
21613    cx.assert_editor_state(&original_state);
21614    let editors = cx.update_workspace(|workspace, _, cx| {
21615        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21616    });
21617    cx.update_editor(|_, _, _| {
21618        assert_eq!(
21619            editors.len(),
21620            1,
21621            "After unsuccessful fallback, no other editor should have been opened"
21622        );
21623    });
21624}
21625
21626#[gpui::test]
21627async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21628    init_test(cx, |_| {});
21629    let mut cx = EditorLspTestContext::new_rust(
21630        lsp::ServerCapabilities {
21631            references_provider: Some(lsp::OneOf::Left(true)),
21632            ..lsp::ServerCapabilities::default()
21633        },
21634        cx,
21635    )
21636    .await;
21637
21638    cx.set_state(
21639        &r#"
21640        fn one() {
21641            let mut a = two();
21642        }
21643
21644        fn ˇtwo() {}"#
21645            .unindent(),
21646    );
21647    cx.lsp
21648        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21649            Ok(Some(vec![
21650                lsp::Location {
21651                    uri: params.text_document_position.text_document.uri.clone(),
21652                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21653                },
21654                lsp::Location {
21655                    uri: params.text_document_position.text_document.uri,
21656                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21657                },
21658            ]))
21659        });
21660    let navigated = cx
21661        .update_editor(|editor, window, cx| {
21662            editor.find_all_references(&FindAllReferences, window, cx)
21663        })
21664        .unwrap()
21665        .await
21666        .expect("Failed to navigate to references");
21667    assert_eq!(
21668        navigated,
21669        Navigated::Yes,
21670        "Should have navigated to references from the FindAllReferences response"
21671    );
21672    cx.assert_editor_state(
21673        &r#"fn one() {
21674            let mut a = two();
21675        }
21676
21677        fn ˇtwo() {}"#
21678            .unindent(),
21679    );
21680
21681    let editors = cx.update_workspace(|workspace, _, cx| {
21682        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21683    });
21684    cx.update_editor(|_, _, _| {
21685        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21686    });
21687
21688    cx.set_state(
21689        &r#"fn one() {
21690            let mut a = ˇtwo();
21691        }
21692
21693        fn two() {}"#
21694            .unindent(),
21695    );
21696    let navigated = cx
21697        .update_editor(|editor, window, cx| {
21698            editor.find_all_references(&FindAllReferences, window, cx)
21699        })
21700        .unwrap()
21701        .await
21702        .expect("Failed to navigate to references");
21703    assert_eq!(
21704        navigated,
21705        Navigated::Yes,
21706        "Should have navigated to references from the FindAllReferences response"
21707    );
21708    cx.assert_editor_state(
21709        &r#"fn one() {
21710            let mut a = ˇtwo();
21711        }
21712
21713        fn two() {}"#
21714            .unindent(),
21715    );
21716    let editors = cx.update_workspace(|workspace, _, cx| {
21717        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21718    });
21719    cx.update_editor(|_, _, _| {
21720        assert_eq!(
21721            editors.len(),
21722            2,
21723            "should have re-used the previous multibuffer"
21724        );
21725    });
21726
21727    cx.set_state(
21728        &r#"fn one() {
21729            let mut a = ˇtwo();
21730        }
21731        fn three() {}
21732        fn two() {}"#
21733            .unindent(),
21734    );
21735    cx.lsp
21736        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21737            Ok(Some(vec![
21738                lsp::Location {
21739                    uri: params.text_document_position.text_document.uri.clone(),
21740                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21741                },
21742                lsp::Location {
21743                    uri: params.text_document_position.text_document.uri,
21744                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21745                },
21746            ]))
21747        });
21748    let navigated = cx
21749        .update_editor(|editor, window, cx| {
21750            editor.find_all_references(&FindAllReferences, window, cx)
21751        })
21752        .unwrap()
21753        .await
21754        .expect("Failed to navigate to references");
21755    assert_eq!(
21756        navigated,
21757        Navigated::Yes,
21758        "Should have navigated to references from the FindAllReferences response"
21759    );
21760    cx.assert_editor_state(
21761        &r#"fn one() {
21762                let mut a = ˇtwo();
21763            }
21764            fn three() {}
21765            fn two() {}"#
21766            .unindent(),
21767    );
21768    let editors = cx.update_workspace(|workspace, _, cx| {
21769        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21770    });
21771    cx.update_editor(|_, _, _| {
21772        assert_eq!(
21773            editors.len(),
21774            3,
21775            "should have used a new multibuffer as offsets changed"
21776        );
21777    });
21778}
21779#[gpui::test]
21780async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21781    init_test(cx, |_| {});
21782
21783    let language = Arc::new(Language::new(
21784        LanguageConfig::default(),
21785        Some(tree_sitter_rust::LANGUAGE.into()),
21786    ));
21787
21788    let text = r#"
21789        #[cfg(test)]
21790        mod tests() {
21791            #[test]
21792            fn runnable_1() {
21793                let a = 1;
21794            }
21795
21796            #[test]
21797            fn runnable_2() {
21798                let a = 1;
21799                let b = 2;
21800            }
21801        }
21802    "#
21803    .unindent();
21804
21805    let fs = FakeFs::new(cx.executor());
21806    fs.insert_file("/file.rs", Default::default()).await;
21807
21808    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21809    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21810    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21811    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21812    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21813
21814    let editor = cx.new_window_entity(|window, cx| {
21815        Editor::new(
21816            EditorMode::full(),
21817            multi_buffer,
21818            Some(project.clone()),
21819            window,
21820            cx,
21821        )
21822    });
21823
21824    editor.update_in(cx, |editor, window, cx| {
21825        let snapshot = editor.buffer().read(cx).snapshot(cx);
21826        editor.tasks.insert(
21827            (buffer.read(cx).remote_id(), 3),
21828            RunnableTasks {
21829                templates: vec![],
21830                offset: snapshot.anchor_before(43),
21831                column: 0,
21832                extra_variables: HashMap::default(),
21833                context_range: BufferOffset(43)..BufferOffset(85),
21834            },
21835        );
21836        editor.tasks.insert(
21837            (buffer.read(cx).remote_id(), 8),
21838            RunnableTasks {
21839                templates: vec![],
21840                offset: snapshot.anchor_before(86),
21841                column: 0,
21842                extra_variables: HashMap::default(),
21843                context_range: BufferOffset(86)..BufferOffset(191),
21844            },
21845        );
21846
21847        // Test finding task when cursor is inside function body
21848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21849            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21850        });
21851        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21852        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21853
21854        // Test finding task when cursor is on function name
21855        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21856            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21857        });
21858        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21859        assert_eq!(row, 8, "Should find task when cursor is on function name");
21860    });
21861}
21862
21863#[gpui::test]
21864async fn test_folding_buffers(cx: &mut TestAppContext) {
21865    init_test(cx, |_| {});
21866
21867    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21868    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21869    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21870
21871    let fs = FakeFs::new(cx.executor());
21872    fs.insert_tree(
21873        path!("/a"),
21874        json!({
21875            "first.rs": sample_text_1,
21876            "second.rs": sample_text_2,
21877            "third.rs": sample_text_3,
21878        }),
21879    )
21880    .await;
21881    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21882    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21883    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21884    let worktree = project.update(cx, |project, cx| {
21885        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21886        assert_eq!(worktrees.len(), 1);
21887        worktrees.pop().unwrap()
21888    });
21889    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21890
21891    let buffer_1 = project
21892        .update(cx, |project, cx| {
21893            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21894        })
21895        .await
21896        .unwrap();
21897    let buffer_2 = project
21898        .update(cx, |project, cx| {
21899            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21900        })
21901        .await
21902        .unwrap();
21903    let buffer_3 = project
21904        .update(cx, |project, cx| {
21905            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21906        })
21907        .await
21908        .unwrap();
21909
21910    let multi_buffer = cx.new(|cx| {
21911        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21912        multi_buffer.push_excerpts(
21913            buffer_1.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.push_excerpts(
21922            buffer_2.clone(),
21923            [
21924                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21925                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21926                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21927            ],
21928            cx,
21929        );
21930        multi_buffer.push_excerpts(
21931            buffer_3.clone(),
21932            [
21933                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21934                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21935                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21936            ],
21937            cx,
21938        );
21939        multi_buffer
21940    });
21941    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21942        Editor::new(
21943            EditorMode::full(),
21944            multi_buffer.clone(),
21945            Some(project.clone()),
21946            window,
21947            cx,
21948        )
21949    });
21950
21951    assert_eq!(
21952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21953        "\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",
21954    );
21955
21956    multi_buffer_editor.update(cx, |editor, cx| {
21957        editor.fold_buffer(buffer_1.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\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21962        "After folding the first buffer, its text should not be displayed"
21963    );
21964
21965    multi_buffer_editor.update(cx, |editor, cx| {
21966        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21967    });
21968    assert_eq!(
21969        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21970        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21971        "After folding the second buffer, its text should not be displayed"
21972    );
21973
21974    multi_buffer_editor.update(cx, |editor, cx| {
21975        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21976    });
21977    assert_eq!(
21978        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21979        "\n\n\n\n\n",
21980        "After folding the third buffer, its text should not be displayed"
21981    );
21982
21983    // Emulate selection inside the fold logic, that should work
21984    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21985        editor
21986            .snapshot(window, cx)
21987            .next_line_boundary(Point::new(0, 4));
21988    });
21989
21990    multi_buffer_editor.update(cx, |editor, cx| {
21991        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21992    });
21993    assert_eq!(
21994        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21995        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21996        "After unfolding the second buffer, its text should be displayed"
21997    );
21998
21999    // Typing inside of buffer 1 causes that buffer to be unfolded.
22000    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22001        assert_eq!(
22002            multi_buffer
22003                .read(cx)
22004                .snapshot(cx)
22005                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22006                .collect::<String>(),
22007            "bbbb"
22008        );
22009        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22010            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22011        });
22012        editor.handle_input("B", window, cx);
22013    });
22014
22015    assert_eq!(
22016        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22017        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22018        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22019    );
22020
22021    multi_buffer_editor.update(cx, |editor, cx| {
22022        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22023    });
22024    assert_eq!(
22025        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22026        "\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",
22027        "After unfolding the all buffers, all original text should be displayed"
22028    );
22029}
22030
22031#[gpui::test]
22032async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22033    init_test(cx, |_| {});
22034
22035    let sample_text_1 = "1111\n2222\n3333".to_string();
22036    let sample_text_2 = "4444\n5555\n6666".to_string();
22037    let sample_text_3 = "7777\n8888\n9999".to_string();
22038
22039    let fs = FakeFs::new(cx.executor());
22040    fs.insert_tree(
22041        path!("/a"),
22042        json!({
22043            "first.rs": sample_text_1,
22044            "second.rs": sample_text_2,
22045            "third.rs": sample_text_3,
22046        }),
22047    )
22048    .await;
22049    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22050    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22051    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22052    let worktree = project.update(cx, |project, cx| {
22053        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22054        assert_eq!(worktrees.len(), 1);
22055        worktrees.pop().unwrap()
22056    });
22057    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22058
22059    let buffer_1 = project
22060        .update(cx, |project, cx| {
22061            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22062        })
22063        .await
22064        .unwrap();
22065    let buffer_2 = project
22066        .update(cx, |project, cx| {
22067            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22068        })
22069        .await
22070        .unwrap();
22071    let buffer_3 = project
22072        .update(cx, |project, cx| {
22073            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22074        })
22075        .await
22076        .unwrap();
22077
22078    let multi_buffer = cx.new(|cx| {
22079        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22080        multi_buffer.push_excerpts(
22081            buffer_1.clone(),
22082            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22083            cx,
22084        );
22085        multi_buffer.push_excerpts(
22086            buffer_2.clone(),
22087            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22088            cx,
22089        );
22090        multi_buffer.push_excerpts(
22091            buffer_3.clone(),
22092            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22093            cx,
22094        );
22095        multi_buffer
22096    });
22097
22098    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22099        Editor::new(
22100            EditorMode::full(),
22101            multi_buffer,
22102            Some(project.clone()),
22103            window,
22104            cx,
22105        )
22106    });
22107
22108    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22109    assert_eq!(
22110        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22111        full_text,
22112    );
22113
22114    multi_buffer_editor.update(cx, |editor, cx| {
22115        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22116    });
22117    assert_eq!(
22118        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22119        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22120        "After folding the first buffer, its text should not be displayed"
22121    );
22122
22123    multi_buffer_editor.update(cx, |editor, cx| {
22124        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22125    });
22126
22127    assert_eq!(
22128        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22129        "\n\n\n\n\n\n7777\n8888\n9999",
22130        "After folding the second buffer, its text should not be displayed"
22131    );
22132
22133    multi_buffer_editor.update(cx, |editor, cx| {
22134        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22135    });
22136    assert_eq!(
22137        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22138        "\n\n\n\n\n",
22139        "After folding the third buffer, its text should not be displayed"
22140    );
22141
22142    multi_buffer_editor.update(cx, |editor, cx| {
22143        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22144    });
22145    assert_eq!(
22146        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22147        "\n\n\n\n4444\n5555\n6666\n\n",
22148        "After unfolding the second buffer, its text should be displayed"
22149    );
22150
22151    multi_buffer_editor.update(cx, |editor, cx| {
22152        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22153    });
22154    assert_eq!(
22155        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22156        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22157        "After unfolding the first buffer, its text should be displayed"
22158    );
22159
22160    multi_buffer_editor.update(cx, |editor, cx| {
22161        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22162    });
22163    assert_eq!(
22164        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22165        full_text,
22166        "After unfolding all buffers, all original text should be displayed"
22167    );
22168}
22169
22170#[gpui::test]
22171async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22172    init_test(cx, |_| {});
22173
22174    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22175
22176    let fs = FakeFs::new(cx.executor());
22177    fs.insert_tree(
22178        path!("/a"),
22179        json!({
22180            "main.rs": sample_text,
22181        }),
22182    )
22183    .await;
22184    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22185    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22186    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22187    let worktree = project.update(cx, |project, cx| {
22188        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22189        assert_eq!(worktrees.len(), 1);
22190        worktrees.pop().unwrap()
22191    });
22192    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22193
22194    let buffer_1 = project
22195        .update(cx, |project, cx| {
22196            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22197        })
22198        .await
22199        .unwrap();
22200
22201    let multi_buffer = cx.new(|cx| {
22202        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22203        multi_buffer.push_excerpts(
22204            buffer_1.clone(),
22205            [ExcerptRange::new(
22206                Point::new(0, 0)
22207                    ..Point::new(
22208                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22209                        0,
22210                    ),
22211            )],
22212            cx,
22213        );
22214        multi_buffer
22215    });
22216    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22217        Editor::new(
22218            EditorMode::full(),
22219            multi_buffer,
22220            Some(project.clone()),
22221            window,
22222            cx,
22223        )
22224    });
22225
22226    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22227    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22228        enum TestHighlight {}
22229        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22230        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22231        editor.highlight_text::<TestHighlight>(
22232            vec![highlight_range.clone()],
22233            HighlightStyle::color(Hsla::green()),
22234            cx,
22235        );
22236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22237            s.select_ranges(Some(highlight_range))
22238        });
22239    });
22240
22241    let full_text = format!("\n\n{sample_text}");
22242    assert_eq!(
22243        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22244        full_text,
22245    );
22246}
22247
22248#[gpui::test]
22249async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22250    init_test(cx, |_| {});
22251    cx.update(|cx| {
22252        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22253            "keymaps/default-linux.json",
22254            cx,
22255        )
22256        .unwrap();
22257        cx.bind_keys(default_key_bindings);
22258    });
22259
22260    let (editor, cx) = cx.add_window_view(|window, cx| {
22261        let multi_buffer = MultiBuffer::build_multi(
22262            [
22263                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22264                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22265                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22266                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22267            ],
22268            cx,
22269        );
22270        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22271
22272        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22273        // fold all but the second buffer, so that we test navigating between two
22274        // adjacent folded buffers, as well as folded buffers at the start and
22275        // end the multibuffer
22276        editor.fold_buffer(buffer_ids[0], cx);
22277        editor.fold_buffer(buffer_ids[2], cx);
22278        editor.fold_buffer(buffer_ids[3], cx);
22279
22280        editor
22281    });
22282    cx.simulate_resize(size(px(1000.), px(1000.)));
22283
22284    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22285    cx.assert_excerpts_with_selections(indoc! {"
22286        [EXCERPT]
22287        ˇ[FOLDED]
22288        [EXCERPT]
22289        a1
22290        b1
22291        [EXCERPT]
22292        [FOLDED]
22293        [EXCERPT]
22294        [FOLDED]
22295        "
22296    });
22297    cx.simulate_keystroke("down");
22298    cx.assert_excerpts_with_selections(indoc! {"
22299        [EXCERPT]
22300        [FOLDED]
22301        [EXCERPT]
22302        ˇa1
22303        b1
22304        [EXCERPT]
22305        [FOLDED]
22306        [EXCERPT]
22307        [FOLDED]
22308        "
22309    });
22310    cx.simulate_keystroke("down");
22311    cx.assert_excerpts_with_selections(indoc! {"
22312        [EXCERPT]
22313        [FOLDED]
22314        [EXCERPT]
22315        a1
22316        ˇb1
22317        [EXCERPT]
22318        [FOLDED]
22319        [EXCERPT]
22320        [FOLDED]
22321        "
22322    });
22323    cx.simulate_keystroke("down");
22324    cx.assert_excerpts_with_selections(indoc! {"
22325        [EXCERPT]
22326        [FOLDED]
22327        [EXCERPT]
22328        a1
22329        b1
22330        ˇ[EXCERPT]
22331        [FOLDED]
22332        [EXCERPT]
22333        [FOLDED]
22334        "
22335    });
22336    cx.simulate_keystroke("down");
22337    cx.assert_excerpts_with_selections(indoc! {"
22338        [EXCERPT]
22339        [FOLDED]
22340        [EXCERPT]
22341        a1
22342        b1
22343        [EXCERPT]
22344        ˇ[FOLDED]
22345        [EXCERPT]
22346        [FOLDED]
22347        "
22348    });
22349    for _ in 0..5 {
22350        cx.simulate_keystroke("down");
22351        cx.assert_excerpts_with_selections(indoc! {"
22352            [EXCERPT]
22353            [FOLDED]
22354            [EXCERPT]
22355            a1
22356            b1
22357            [EXCERPT]
22358            [FOLDED]
22359            [EXCERPT]
22360            ˇ[FOLDED]
22361            "
22362        });
22363    }
22364
22365    cx.simulate_keystroke("up");
22366    cx.assert_excerpts_with_selections(indoc! {"
22367        [EXCERPT]
22368        [FOLDED]
22369        [EXCERPT]
22370        a1
22371        b1
22372        [EXCERPT]
22373        ˇ[FOLDED]
22374        [EXCERPT]
22375        [FOLDED]
22376        "
22377    });
22378    cx.simulate_keystroke("up");
22379    cx.assert_excerpts_with_selections(indoc! {"
22380        [EXCERPT]
22381        [FOLDED]
22382        [EXCERPT]
22383        a1
22384        b1
22385        ˇ[EXCERPT]
22386        [FOLDED]
22387        [EXCERPT]
22388        [FOLDED]
22389        "
22390    });
22391    cx.simulate_keystroke("up");
22392    cx.assert_excerpts_with_selections(indoc! {"
22393        [EXCERPT]
22394        [FOLDED]
22395        [EXCERPT]
22396        a1
22397        ˇb1
22398        [EXCERPT]
22399        [FOLDED]
22400        [EXCERPT]
22401        [FOLDED]
22402        "
22403    });
22404    cx.simulate_keystroke("up");
22405    cx.assert_excerpts_with_selections(indoc! {"
22406        [EXCERPT]
22407        [FOLDED]
22408        [EXCERPT]
22409        ˇa1
22410        b1
22411        [EXCERPT]
22412        [FOLDED]
22413        [EXCERPT]
22414        [FOLDED]
22415        "
22416    });
22417    for _ in 0..5 {
22418        cx.simulate_keystroke("up");
22419        cx.assert_excerpts_with_selections(indoc! {"
22420            [EXCERPT]
22421            ˇ[FOLDED]
22422            [EXCERPT]
22423            a1
22424            b1
22425            [EXCERPT]
22426            [FOLDED]
22427            [EXCERPT]
22428            [FOLDED]
22429            "
22430        });
22431    }
22432}
22433
22434#[gpui::test]
22435async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22436    init_test(cx, |_| {});
22437
22438    // Simple insertion
22439    assert_highlighted_edits(
22440        "Hello, world!",
22441        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22442        true,
22443        cx,
22444        |highlighted_edits, cx| {
22445            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22446            assert_eq!(highlighted_edits.highlights.len(), 1);
22447            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
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    // Replacement
22457    assert_highlighted_edits(
22458        "This is a test.",
22459        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22460        false,
22461        cx,
22462        |highlighted_edits, cx| {
22463            assert_eq!(highlighted_edits.text, "That is a test.");
22464            assert_eq!(highlighted_edits.highlights.len(), 1);
22465            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22466            assert_eq!(
22467                highlighted_edits.highlights[0].1.background_color,
22468                Some(cx.theme().status().created_background)
22469            );
22470        },
22471    )
22472    .await;
22473
22474    // Multiple edits
22475    assert_highlighted_edits(
22476        "Hello, world!",
22477        vec![
22478            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22479            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22480        ],
22481        false,
22482        cx,
22483        |highlighted_edits, cx| {
22484            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22485            assert_eq!(highlighted_edits.highlights.len(), 2);
22486            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22487            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22488            assert_eq!(
22489                highlighted_edits.highlights[0].1.background_color,
22490                Some(cx.theme().status().created_background)
22491            );
22492            assert_eq!(
22493                highlighted_edits.highlights[1].1.background_color,
22494                Some(cx.theme().status().created_background)
22495            );
22496        },
22497    )
22498    .await;
22499
22500    // Multiple lines with edits
22501    assert_highlighted_edits(
22502        "First line\nSecond line\nThird line\nFourth line",
22503        vec![
22504            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22505            (
22506                Point::new(2, 0)..Point::new(2, 10),
22507                "New third line".to_string(),
22508            ),
22509            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22510        ],
22511        false,
22512        cx,
22513        |highlighted_edits, cx| {
22514            assert_eq!(
22515                highlighted_edits.text,
22516                "Second modified\nNew third line\nFourth updated line"
22517            );
22518            assert_eq!(highlighted_edits.highlights.len(), 3);
22519            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22520            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22521            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22522            for highlight in &highlighted_edits.highlights {
22523                assert_eq!(
22524                    highlight.1.background_color,
22525                    Some(cx.theme().status().created_background)
22526                );
22527            }
22528        },
22529    )
22530    .await;
22531}
22532
22533#[gpui::test]
22534async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22535    init_test(cx, |_| {});
22536
22537    // Deletion
22538    assert_highlighted_edits(
22539        "Hello, world!",
22540        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22541        true,
22542        cx,
22543        |highlighted_edits, cx| {
22544            assert_eq!(highlighted_edits.text, "Hello, world!");
22545            assert_eq!(highlighted_edits.highlights.len(), 1);
22546            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22547            assert_eq!(
22548                highlighted_edits.highlights[0].1.background_color,
22549                Some(cx.theme().status().deleted_background)
22550            );
22551        },
22552    )
22553    .await;
22554
22555    // Insertion
22556    assert_highlighted_edits(
22557        "Hello, world!",
22558        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22559        true,
22560        cx,
22561        |highlighted_edits, cx| {
22562            assert_eq!(highlighted_edits.highlights.len(), 1);
22563            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22564            assert_eq!(
22565                highlighted_edits.highlights[0].1.background_color,
22566                Some(cx.theme().status().created_background)
22567            );
22568        },
22569    )
22570    .await;
22571}
22572
22573async fn assert_highlighted_edits(
22574    text: &str,
22575    edits: Vec<(Range<Point>, String)>,
22576    include_deletions: bool,
22577    cx: &mut TestAppContext,
22578    assertion_fn: impl Fn(HighlightedText, &App),
22579) {
22580    let window = cx.add_window(|window, cx| {
22581        let buffer = MultiBuffer::build_simple(text, cx);
22582        Editor::new(EditorMode::full(), buffer, None, window, cx)
22583    });
22584    let cx = &mut VisualTestContext::from_window(*window, cx);
22585
22586    let (buffer, snapshot) = window
22587        .update(cx, |editor, _window, cx| {
22588            (
22589                editor.buffer().clone(),
22590                editor.buffer().read(cx).snapshot(cx),
22591            )
22592        })
22593        .unwrap();
22594
22595    let edits = edits
22596        .into_iter()
22597        .map(|(range, edit)| {
22598            (
22599                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22600                edit,
22601            )
22602        })
22603        .collect::<Vec<_>>();
22604
22605    let text_anchor_edits = edits
22606        .clone()
22607        .into_iter()
22608        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22609        .collect::<Vec<_>>();
22610
22611    let edit_preview = window
22612        .update(cx, |_, _window, cx| {
22613            buffer
22614                .read(cx)
22615                .as_singleton()
22616                .unwrap()
22617                .read(cx)
22618                .preview_edits(text_anchor_edits.into(), cx)
22619        })
22620        .unwrap()
22621        .await;
22622
22623    cx.update(|_window, cx| {
22624        let highlighted_edits = edit_prediction_edit_text(
22625            snapshot.as_singleton().unwrap().2,
22626            &edits,
22627            &edit_preview,
22628            include_deletions,
22629            cx,
22630        );
22631        assertion_fn(highlighted_edits, cx)
22632    });
22633}
22634
22635#[track_caller]
22636fn assert_breakpoint(
22637    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22638    path: &Arc<Path>,
22639    expected: Vec<(u32, Breakpoint)>,
22640) {
22641    if expected.is_empty() {
22642        assert!(!breakpoints.contains_key(path), "{}", path.display());
22643    } else {
22644        let mut breakpoint = breakpoints
22645            .get(path)
22646            .unwrap()
22647            .iter()
22648            .map(|breakpoint| {
22649                (
22650                    breakpoint.row,
22651                    Breakpoint {
22652                        message: breakpoint.message.clone(),
22653                        state: breakpoint.state,
22654                        condition: breakpoint.condition.clone(),
22655                        hit_condition: breakpoint.hit_condition.clone(),
22656                    },
22657                )
22658            })
22659            .collect::<Vec<_>>();
22660
22661        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22662
22663        assert_eq!(expected, breakpoint);
22664    }
22665}
22666
22667fn add_log_breakpoint_at_cursor(
22668    editor: &mut Editor,
22669    log_message: &str,
22670    window: &mut Window,
22671    cx: &mut Context<Editor>,
22672) {
22673    let (anchor, bp) = editor
22674        .breakpoints_at_cursors(window, cx)
22675        .first()
22676        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22677        .unwrap_or_else(|| {
22678            let cursor_position: Point = editor.selections.newest(cx).head();
22679
22680            let breakpoint_position = editor
22681                .snapshot(window, cx)
22682                .display_snapshot
22683                .buffer_snapshot()
22684                .anchor_before(Point::new(cursor_position.row, 0));
22685
22686            (breakpoint_position, Breakpoint::new_log(log_message))
22687        });
22688
22689    editor.edit_breakpoint_at_anchor(
22690        anchor,
22691        bp,
22692        BreakpointEditAction::EditLogMessage(log_message.into()),
22693        cx,
22694    );
22695}
22696
22697#[gpui::test]
22698async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22699    init_test(cx, |_| {});
22700
22701    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22702    let fs = FakeFs::new(cx.executor());
22703    fs.insert_tree(
22704        path!("/a"),
22705        json!({
22706            "main.rs": sample_text,
22707        }),
22708    )
22709    .await;
22710    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22712    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22713
22714    let fs = FakeFs::new(cx.executor());
22715    fs.insert_tree(
22716        path!("/a"),
22717        json!({
22718            "main.rs": sample_text,
22719        }),
22720    )
22721    .await;
22722    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22723    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22724    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22725    let worktree_id = workspace
22726        .update(cx, |workspace, _window, cx| {
22727            workspace.project().update(cx, |project, cx| {
22728                project.worktrees(cx).next().unwrap().read(cx).id()
22729            })
22730        })
22731        .unwrap();
22732
22733    let buffer = project
22734        .update(cx, |project, cx| {
22735            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22736        })
22737        .await
22738        .unwrap();
22739
22740    let (editor, cx) = cx.add_window_view(|window, cx| {
22741        Editor::new(
22742            EditorMode::full(),
22743            MultiBuffer::build_from_buffer(buffer, cx),
22744            Some(project.clone()),
22745            window,
22746            cx,
22747        )
22748    });
22749
22750    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22751    let abs_path = project.read_with(cx, |project, cx| {
22752        project
22753            .absolute_path(&project_path, cx)
22754            .map(Arc::from)
22755            .unwrap()
22756    });
22757
22758    // assert we can add breakpoint on the first line
22759    editor.update_in(cx, |editor, window, cx| {
22760        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22761        editor.move_to_end(&MoveToEnd, window, cx);
22762        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22763    });
22764
22765    let breakpoints = editor.update(cx, |editor, cx| {
22766        editor
22767            .breakpoint_store()
22768            .as_ref()
22769            .unwrap()
22770            .read(cx)
22771            .all_source_breakpoints(cx)
22772    });
22773
22774    assert_eq!(1, breakpoints.len());
22775    assert_breakpoint(
22776        &breakpoints,
22777        &abs_path,
22778        vec![
22779            (0, Breakpoint::new_standard()),
22780            (3, Breakpoint::new_standard()),
22781        ],
22782    );
22783
22784    editor.update_in(cx, |editor, window, cx| {
22785        editor.move_to_beginning(&MoveToBeginning, window, cx);
22786        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22787    });
22788
22789    let breakpoints = editor.update(cx, |editor, cx| {
22790        editor
22791            .breakpoint_store()
22792            .as_ref()
22793            .unwrap()
22794            .read(cx)
22795            .all_source_breakpoints(cx)
22796    });
22797
22798    assert_eq!(1, breakpoints.len());
22799    assert_breakpoint(
22800        &breakpoints,
22801        &abs_path,
22802        vec![(3, Breakpoint::new_standard())],
22803    );
22804
22805    editor.update_in(cx, |editor, window, cx| {
22806        editor.move_to_end(&MoveToEnd, window, cx);
22807        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22808    });
22809
22810    let breakpoints = editor.update(cx, |editor, cx| {
22811        editor
22812            .breakpoint_store()
22813            .as_ref()
22814            .unwrap()
22815            .read(cx)
22816            .all_source_breakpoints(cx)
22817    });
22818
22819    assert_eq!(0, breakpoints.len());
22820    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22821}
22822
22823#[gpui::test]
22824async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22825    init_test(cx, |_| {});
22826
22827    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22828
22829    let fs = FakeFs::new(cx.executor());
22830    fs.insert_tree(
22831        path!("/a"),
22832        json!({
22833            "main.rs": sample_text,
22834        }),
22835    )
22836    .await;
22837    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22838    let (workspace, cx) =
22839        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22840
22841    let worktree_id = workspace.update(cx, |workspace, cx| {
22842        workspace.project().update(cx, |project, cx| {
22843            project.worktrees(cx).next().unwrap().read(cx).id()
22844        })
22845    });
22846
22847    let buffer = project
22848        .update(cx, |project, cx| {
22849            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22850        })
22851        .await
22852        .unwrap();
22853
22854    let (editor, cx) = cx.add_window_view(|window, cx| {
22855        Editor::new(
22856            EditorMode::full(),
22857            MultiBuffer::build_from_buffer(buffer, cx),
22858            Some(project.clone()),
22859            window,
22860            cx,
22861        )
22862    });
22863
22864    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22865    let abs_path = project.read_with(cx, |project, cx| {
22866        project
22867            .absolute_path(&project_path, cx)
22868            .map(Arc::from)
22869            .unwrap()
22870    });
22871
22872    editor.update_in(cx, |editor, window, cx| {
22873        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22874    });
22875
22876    let breakpoints = editor.update(cx, |editor, cx| {
22877        editor
22878            .breakpoint_store()
22879            .as_ref()
22880            .unwrap()
22881            .read(cx)
22882            .all_source_breakpoints(cx)
22883    });
22884
22885    assert_breakpoint(
22886        &breakpoints,
22887        &abs_path,
22888        vec![(0, Breakpoint::new_log("hello world"))],
22889    );
22890
22891    // Removing a log message from a log breakpoint should remove it
22892    editor.update_in(cx, |editor, window, cx| {
22893        add_log_breakpoint_at_cursor(editor, "", window, cx);
22894    });
22895
22896    let breakpoints = editor.update(cx, |editor, cx| {
22897        editor
22898            .breakpoint_store()
22899            .as_ref()
22900            .unwrap()
22901            .read(cx)
22902            .all_source_breakpoints(cx)
22903    });
22904
22905    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22906
22907    editor.update_in(cx, |editor, window, cx| {
22908        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22909        editor.move_to_end(&MoveToEnd, window, cx);
22910        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22911        // Not adding a log message to a standard breakpoint shouldn't remove it
22912        add_log_breakpoint_at_cursor(editor, "", window, cx);
22913    });
22914
22915    let breakpoints = editor.update(cx, |editor, cx| {
22916        editor
22917            .breakpoint_store()
22918            .as_ref()
22919            .unwrap()
22920            .read(cx)
22921            .all_source_breakpoints(cx)
22922    });
22923
22924    assert_breakpoint(
22925        &breakpoints,
22926        &abs_path,
22927        vec![
22928            (0, Breakpoint::new_standard()),
22929            (3, Breakpoint::new_standard()),
22930        ],
22931    );
22932
22933    editor.update_in(cx, |editor, window, cx| {
22934        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22935    });
22936
22937    let breakpoints = editor.update(cx, |editor, cx| {
22938        editor
22939            .breakpoint_store()
22940            .as_ref()
22941            .unwrap()
22942            .read(cx)
22943            .all_source_breakpoints(cx)
22944    });
22945
22946    assert_breakpoint(
22947        &breakpoints,
22948        &abs_path,
22949        vec![
22950            (0, Breakpoint::new_standard()),
22951            (3, Breakpoint::new_log("hello world")),
22952        ],
22953    );
22954
22955    editor.update_in(cx, |editor, window, cx| {
22956        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22957    });
22958
22959    let breakpoints = editor.update(cx, |editor, cx| {
22960        editor
22961            .breakpoint_store()
22962            .as_ref()
22963            .unwrap()
22964            .read(cx)
22965            .all_source_breakpoints(cx)
22966    });
22967
22968    assert_breakpoint(
22969        &breakpoints,
22970        &abs_path,
22971        vec![
22972            (0, Breakpoint::new_standard()),
22973            (3, Breakpoint::new_log("hello Earth!!")),
22974        ],
22975    );
22976}
22977
22978/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22979/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22980/// or when breakpoints were placed out of order. This tests for a regression too
22981#[gpui::test]
22982async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22983    init_test(cx, |_| {});
22984
22985    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22986    let fs = FakeFs::new(cx.executor());
22987    fs.insert_tree(
22988        path!("/a"),
22989        json!({
22990            "main.rs": sample_text,
22991        }),
22992    )
22993    .await;
22994    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22995    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22996    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22997
22998    let fs = FakeFs::new(cx.executor());
22999    fs.insert_tree(
23000        path!("/a"),
23001        json!({
23002            "main.rs": sample_text,
23003        }),
23004    )
23005    .await;
23006    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23007    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23008    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23009    let worktree_id = workspace
23010        .update(cx, |workspace, _window, cx| {
23011            workspace.project().update(cx, |project, cx| {
23012                project.worktrees(cx).next().unwrap().read(cx).id()
23013            })
23014        })
23015        .unwrap();
23016
23017    let buffer = project
23018        .update(cx, |project, cx| {
23019            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23020        })
23021        .await
23022        .unwrap();
23023
23024    let (editor, cx) = cx.add_window_view(|window, cx| {
23025        Editor::new(
23026            EditorMode::full(),
23027            MultiBuffer::build_from_buffer(buffer, cx),
23028            Some(project.clone()),
23029            window,
23030            cx,
23031        )
23032    });
23033
23034    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23035    let abs_path = project.read_with(cx, |project, cx| {
23036        project
23037            .absolute_path(&project_path, cx)
23038            .map(Arc::from)
23039            .unwrap()
23040    });
23041
23042    // assert we can add breakpoint on the first line
23043    editor.update_in(cx, |editor, window, cx| {
23044        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23045        editor.move_to_end(&MoveToEnd, window, cx);
23046        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23047        editor.move_up(&MoveUp, window, cx);
23048        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23049    });
23050
23051    let breakpoints = editor.update(cx, |editor, cx| {
23052        editor
23053            .breakpoint_store()
23054            .as_ref()
23055            .unwrap()
23056            .read(cx)
23057            .all_source_breakpoints(cx)
23058    });
23059
23060    assert_eq!(1, breakpoints.len());
23061    assert_breakpoint(
23062        &breakpoints,
23063        &abs_path,
23064        vec![
23065            (0, Breakpoint::new_standard()),
23066            (2, Breakpoint::new_standard()),
23067            (3, Breakpoint::new_standard()),
23068        ],
23069    );
23070
23071    editor.update_in(cx, |editor, window, cx| {
23072        editor.move_to_beginning(&MoveToBeginning, window, cx);
23073        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23074        editor.move_to_end(&MoveToEnd, window, cx);
23075        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23076        // Disabling a breakpoint that doesn't exist should do nothing
23077        editor.move_up(&MoveUp, window, cx);
23078        editor.move_up(&MoveUp, window, cx);
23079        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23080    });
23081
23082    let breakpoints = editor.update(cx, |editor, cx| {
23083        editor
23084            .breakpoint_store()
23085            .as_ref()
23086            .unwrap()
23087            .read(cx)
23088            .all_source_breakpoints(cx)
23089    });
23090
23091    let disable_breakpoint = {
23092        let mut bp = Breakpoint::new_standard();
23093        bp.state = BreakpointState::Disabled;
23094        bp
23095    };
23096
23097    assert_eq!(1, breakpoints.len());
23098    assert_breakpoint(
23099        &breakpoints,
23100        &abs_path,
23101        vec![
23102            (0, disable_breakpoint.clone()),
23103            (2, Breakpoint::new_standard()),
23104            (3, disable_breakpoint.clone()),
23105        ],
23106    );
23107
23108    editor.update_in(cx, |editor, window, cx| {
23109        editor.move_to_beginning(&MoveToBeginning, window, cx);
23110        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23111        editor.move_to_end(&MoveToEnd, window, cx);
23112        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23113        editor.move_up(&MoveUp, window, cx);
23114        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23115    });
23116
23117    let breakpoints = editor.update(cx, |editor, cx| {
23118        editor
23119            .breakpoint_store()
23120            .as_ref()
23121            .unwrap()
23122            .read(cx)
23123            .all_source_breakpoints(cx)
23124    });
23125
23126    assert_eq!(1, breakpoints.len());
23127    assert_breakpoint(
23128        &breakpoints,
23129        &abs_path,
23130        vec![
23131            (0, Breakpoint::new_standard()),
23132            (2, disable_breakpoint),
23133            (3, Breakpoint::new_standard()),
23134        ],
23135    );
23136}
23137
23138#[gpui::test]
23139async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23140    init_test(cx, |_| {});
23141    let capabilities = lsp::ServerCapabilities {
23142        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23143            prepare_provider: Some(true),
23144            work_done_progress_options: Default::default(),
23145        })),
23146        ..Default::default()
23147    };
23148    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23149
23150    cx.set_state(indoc! {"
23151        struct Fˇoo {}
23152    "});
23153
23154    cx.update_editor(|editor, _, cx| {
23155        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23156        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23157        editor.highlight_background::<DocumentHighlightRead>(
23158            &[highlight_range],
23159            |theme| theme.colors().editor_document_highlight_read_background,
23160            cx,
23161        );
23162    });
23163
23164    let mut prepare_rename_handler = cx
23165        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23166            move |_, _, _| async move {
23167                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23168                    start: lsp::Position {
23169                        line: 0,
23170                        character: 7,
23171                    },
23172                    end: lsp::Position {
23173                        line: 0,
23174                        character: 10,
23175                    },
23176                })))
23177            },
23178        );
23179    let prepare_rename_task = cx
23180        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23181        .expect("Prepare rename was not started");
23182    prepare_rename_handler.next().await.unwrap();
23183    prepare_rename_task.await.expect("Prepare rename failed");
23184
23185    let mut rename_handler =
23186        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23187            let edit = lsp::TextEdit {
23188                range: lsp::Range {
23189                    start: lsp::Position {
23190                        line: 0,
23191                        character: 7,
23192                    },
23193                    end: lsp::Position {
23194                        line: 0,
23195                        character: 10,
23196                    },
23197                },
23198                new_text: "FooRenamed".to_string(),
23199            };
23200            Ok(Some(lsp::WorkspaceEdit::new(
23201                // Specify the same edit twice
23202                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23203            )))
23204        });
23205    let rename_task = cx
23206        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23207        .expect("Confirm rename was not started");
23208    rename_handler.next().await.unwrap();
23209    rename_task.await.expect("Confirm rename failed");
23210    cx.run_until_parked();
23211
23212    // Despite two edits, only one is actually applied as those are identical
23213    cx.assert_editor_state(indoc! {"
23214        struct FooRenamedˇ {}
23215    "});
23216}
23217
23218#[gpui::test]
23219async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23220    init_test(cx, |_| {});
23221    // These capabilities indicate that the server does not support prepare rename.
23222    let capabilities = lsp::ServerCapabilities {
23223        rename_provider: Some(lsp::OneOf::Left(true)),
23224        ..Default::default()
23225    };
23226    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23227
23228    cx.set_state(indoc! {"
23229        struct Fˇoo {}
23230    "});
23231
23232    cx.update_editor(|editor, _window, cx| {
23233        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23234        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23235        editor.highlight_background::<DocumentHighlightRead>(
23236            &[highlight_range],
23237            |theme| theme.colors().editor_document_highlight_read_background,
23238            cx,
23239        );
23240    });
23241
23242    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23243        .expect("Prepare rename was not started")
23244        .await
23245        .expect("Prepare rename failed");
23246
23247    let mut rename_handler =
23248        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23249            let edit = lsp::TextEdit {
23250                range: lsp::Range {
23251                    start: lsp::Position {
23252                        line: 0,
23253                        character: 7,
23254                    },
23255                    end: lsp::Position {
23256                        line: 0,
23257                        character: 10,
23258                    },
23259                },
23260                new_text: "FooRenamed".to_string(),
23261            };
23262            Ok(Some(lsp::WorkspaceEdit::new(
23263                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23264            )))
23265        });
23266    let rename_task = cx
23267        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23268        .expect("Confirm rename was not started");
23269    rename_handler.next().await.unwrap();
23270    rename_task.await.expect("Confirm rename failed");
23271    cx.run_until_parked();
23272
23273    // Correct range is renamed, as `surrounding_word` is used to find it.
23274    cx.assert_editor_state(indoc! {"
23275        struct FooRenamedˇ {}
23276    "});
23277}
23278
23279#[gpui::test]
23280async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23281    init_test(cx, |_| {});
23282    let mut cx = EditorTestContext::new(cx).await;
23283
23284    let language = Arc::new(
23285        Language::new(
23286            LanguageConfig::default(),
23287            Some(tree_sitter_html::LANGUAGE.into()),
23288        )
23289        .with_brackets_query(
23290            r#"
23291            ("<" @open "/>" @close)
23292            ("</" @open ">" @close)
23293            ("<" @open ">" @close)
23294            ("\"" @open "\"" @close)
23295            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23296        "#,
23297        )
23298        .unwrap(),
23299    );
23300    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23301
23302    cx.set_state(indoc! {"
23303        <span>ˇ</span>
23304    "});
23305    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23306    cx.assert_editor_state(indoc! {"
23307        <span>
23308        ˇ
23309        </span>
23310    "});
23311
23312    cx.set_state(indoc! {"
23313        <span><span></span>ˇ</span>
23314    "});
23315    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23316    cx.assert_editor_state(indoc! {"
23317        <span><span></span>
23318        ˇ</span>
23319    "});
23320
23321    cx.set_state(indoc! {"
23322        <span>ˇ
23323        </span>
23324    "});
23325    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23326    cx.assert_editor_state(indoc! {"
23327        <span>
23328        ˇ
23329        </span>
23330    "});
23331}
23332
23333#[gpui::test(iterations = 10)]
23334async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23335    init_test(cx, |_| {});
23336
23337    let fs = FakeFs::new(cx.executor());
23338    fs.insert_tree(
23339        path!("/dir"),
23340        json!({
23341            "a.ts": "a",
23342        }),
23343    )
23344    .await;
23345
23346    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23347    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23348    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23349
23350    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23351    language_registry.add(Arc::new(Language::new(
23352        LanguageConfig {
23353            name: "TypeScript".into(),
23354            matcher: LanguageMatcher {
23355                path_suffixes: vec!["ts".to_string()],
23356                ..Default::default()
23357            },
23358            ..Default::default()
23359        },
23360        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23361    )));
23362    let mut fake_language_servers = language_registry.register_fake_lsp(
23363        "TypeScript",
23364        FakeLspAdapter {
23365            capabilities: lsp::ServerCapabilities {
23366                code_lens_provider: Some(lsp::CodeLensOptions {
23367                    resolve_provider: Some(true),
23368                }),
23369                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23370                    commands: vec!["_the/command".to_string()],
23371                    ..lsp::ExecuteCommandOptions::default()
23372                }),
23373                ..lsp::ServerCapabilities::default()
23374            },
23375            ..FakeLspAdapter::default()
23376        },
23377    );
23378
23379    let editor = workspace
23380        .update(cx, |workspace, window, cx| {
23381            workspace.open_abs_path(
23382                PathBuf::from(path!("/dir/a.ts")),
23383                OpenOptions::default(),
23384                window,
23385                cx,
23386            )
23387        })
23388        .unwrap()
23389        .await
23390        .unwrap()
23391        .downcast::<Editor>()
23392        .unwrap();
23393    cx.executor().run_until_parked();
23394
23395    let fake_server = fake_language_servers.next().await.unwrap();
23396
23397    let buffer = editor.update(cx, |editor, cx| {
23398        editor
23399            .buffer()
23400            .read(cx)
23401            .as_singleton()
23402            .expect("have opened a single file by path")
23403    });
23404
23405    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23406    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23407    drop(buffer_snapshot);
23408    let actions = cx
23409        .update_window(*workspace, |_, window, cx| {
23410            project.code_actions(&buffer, anchor..anchor, window, cx)
23411        })
23412        .unwrap();
23413
23414    fake_server
23415        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23416            Ok(Some(vec![
23417                lsp::CodeLens {
23418                    range: lsp::Range::default(),
23419                    command: Some(lsp::Command {
23420                        title: "Code lens command".to_owned(),
23421                        command: "_the/command".to_owned(),
23422                        arguments: None,
23423                    }),
23424                    data: None,
23425                },
23426                lsp::CodeLens {
23427                    range: lsp::Range::default(),
23428                    command: Some(lsp::Command {
23429                        title: "Command not in capabilities".to_owned(),
23430                        command: "not in capabilities".to_owned(),
23431                        arguments: None,
23432                    }),
23433                    data: None,
23434                },
23435                lsp::CodeLens {
23436                    range: lsp::Range {
23437                        start: lsp::Position {
23438                            line: 1,
23439                            character: 1,
23440                        },
23441                        end: lsp::Position {
23442                            line: 1,
23443                            character: 1,
23444                        },
23445                    },
23446                    command: Some(lsp::Command {
23447                        title: "Command not in range".to_owned(),
23448                        command: "_the/command".to_owned(),
23449                        arguments: None,
23450                    }),
23451                    data: None,
23452                },
23453            ]))
23454        })
23455        .next()
23456        .await;
23457
23458    let actions = actions.await.unwrap();
23459    assert_eq!(
23460        actions.len(),
23461        1,
23462        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23463    );
23464    let action = actions[0].clone();
23465    let apply = project.update(cx, |project, cx| {
23466        project.apply_code_action(buffer.clone(), action, true, cx)
23467    });
23468
23469    // Resolving the code action does not populate its edits. In absence of
23470    // edits, we must execute the given command.
23471    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23472        |mut lens, _| async move {
23473            let lens_command = lens.command.as_mut().expect("should have a command");
23474            assert_eq!(lens_command.title, "Code lens command");
23475            lens_command.arguments = Some(vec![json!("the-argument")]);
23476            Ok(lens)
23477        },
23478    );
23479
23480    // While executing the command, the language server sends the editor
23481    // a `workspaceEdit` request.
23482    fake_server
23483        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23484            let fake = fake_server.clone();
23485            move |params, _| {
23486                assert_eq!(params.command, "_the/command");
23487                let fake = fake.clone();
23488                async move {
23489                    fake.server
23490                        .request::<lsp::request::ApplyWorkspaceEdit>(
23491                            lsp::ApplyWorkspaceEditParams {
23492                                label: None,
23493                                edit: lsp::WorkspaceEdit {
23494                                    changes: Some(
23495                                        [(
23496                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23497                                            vec![lsp::TextEdit {
23498                                                range: lsp::Range::new(
23499                                                    lsp::Position::new(0, 0),
23500                                                    lsp::Position::new(0, 0),
23501                                                ),
23502                                                new_text: "X".into(),
23503                                            }],
23504                                        )]
23505                                        .into_iter()
23506                                        .collect(),
23507                                    ),
23508                                    ..lsp::WorkspaceEdit::default()
23509                                },
23510                            },
23511                        )
23512                        .await
23513                        .into_response()
23514                        .unwrap();
23515                    Ok(Some(json!(null)))
23516                }
23517            }
23518        })
23519        .next()
23520        .await;
23521
23522    // Applying the code lens command returns a project transaction containing the edits
23523    // sent by the language server in its `workspaceEdit` request.
23524    let transaction = apply.await.unwrap();
23525    assert!(transaction.0.contains_key(&buffer));
23526    buffer.update(cx, |buffer, cx| {
23527        assert_eq!(buffer.text(), "Xa");
23528        buffer.undo(cx);
23529        assert_eq!(buffer.text(), "a");
23530    });
23531
23532    let actions_after_edits = cx
23533        .update_window(*workspace, |_, window, cx| {
23534            project.code_actions(&buffer, anchor..anchor, window, cx)
23535        })
23536        .unwrap()
23537        .await
23538        .unwrap();
23539    assert_eq!(
23540        actions, actions_after_edits,
23541        "For the same selection, same code lens actions should be returned"
23542    );
23543
23544    let _responses =
23545        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23546            panic!("No more code lens requests are expected");
23547        });
23548    editor.update_in(cx, |editor, window, cx| {
23549        editor.select_all(&SelectAll, window, cx);
23550    });
23551    cx.executor().run_until_parked();
23552    let new_actions = cx
23553        .update_window(*workspace, |_, window, cx| {
23554            project.code_actions(&buffer, anchor..anchor, window, cx)
23555        })
23556        .unwrap()
23557        .await
23558        .unwrap();
23559    assert_eq!(
23560        actions, new_actions,
23561        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23562    );
23563}
23564
23565#[gpui::test]
23566async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23567    init_test(cx, |_| {});
23568
23569    let fs = FakeFs::new(cx.executor());
23570    let main_text = r#"fn main() {
23571println!("1");
23572println!("2");
23573println!("3");
23574println!("4");
23575println!("5");
23576}"#;
23577    let lib_text = "mod foo {}";
23578    fs.insert_tree(
23579        path!("/a"),
23580        json!({
23581            "lib.rs": lib_text,
23582            "main.rs": main_text,
23583        }),
23584    )
23585    .await;
23586
23587    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23588    let (workspace, cx) =
23589        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23590    let worktree_id = workspace.update(cx, |workspace, cx| {
23591        workspace.project().update(cx, |project, cx| {
23592            project.worktrees(cx).next().unwrap().read(cx).id()
23593        })
23594    });
23595
23596    let expected_ranges = vec![
23597        Point::new(0, 0)..Point::new(0, 0),
23598        Point::new(1, 0)..Point::new(1, 1),
23599        Point::new(2, 0)..Point::new(2, 2),
23600        Point::new(3, 0)..Point::new(3, 3),
23601    ];
23602
23603    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23604    let editor_1 = workspace
23605        .update_in(cx, |workspace, window, cx| {
23606            workspace.open_path(
23607                (worktree_id, rel_path("main.rs")),
23608                Some(pane_1.downgrade()),
23609                true,
23610                window,
23611                cx,
23612            )
23613        })
23614        .unwrap()
23615        .await
23616        .downcast::<Editor>()
23617        .unwrap();
23618    pane_1.update(cx, |pane, cx| {
23619        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23620        open_editor.update(cx, |editor, cx| {
23621            assert_eq!(
23622                editor.display_text(cx),
23623                main_text,
23624                "Original main.rs text on initial open",
23625            );
23626            assert_eq!(
23627                editor
23628                    .selections
23629                    .all::<Point>(cx)
23630                    .into_iter()
23631                    .map(|s| s.range())
23632                    .collect::<Vec<_>>(),
23633                vec![Point::zero()..Point::zero()],
23634                "Default selections on initial open",
23635            );
23636        })
23637    });
23638    editor_1.update_in(cx, |editor, window, cx| {
23639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23640            s.select_ranges(expected_ranges.clone());
23641        });
23642    });
23643
23644    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23645        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23646    });
23647    let editor_2 = workspace
23648        .update_in(cx, |workspace, window, cx| {
23649            workspace.open_path(
23650                (worktree_id, rel_path("main.rs")),
23651                Some(pane_2.downgrade()),
23652                true,
23653                window,
23654                cx,
23655            )
23656        })
23657        .unwrap()
23658        .await
23659        .downcast::<Editor>()
23660        .unwrap();
23661    pane_2.update(cx, |pane, cx| {
23662        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23663        open_editor.update(cx, |editor, cx| {
23664            assert_eq!(
23665                editor.display_text(cx),
23666                main_text,
23667                "Original main.rs text on initial open in another panel",
23668            );
23669            assert_eq!(
23670                editor
23671                    .selections
23672                    .all::<Point>(cx)
23673                    .into_iter()
23674                    .map(|s| s.range())
23675                    .collect::<Vec<_>>(),
23676                vec![Point::zero()..Point::zero()],
23677                "Default selections on initial open in another panel",
23678            );
23679        })
23680    });
23681
23682    editor_2.update_in(cx, |editor, window, cx| {
23683        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23684    });
23685
23686    let _other_editor_1 = workspace
23687        .update_in(cx, |workspace, window, cx| {
23688            workspace.open_path(
23689                (worktree_id, rel_path("lib.rs")),
23690                Some(pane_1.downgrade()),
23691                true,
23692                window,
23693                cx,
23694            )
23695        })
23696        .unwrap()
23697        .await
23698        .downcast::<Editor>()
23699        .unwrap();
23700    pane_1
23701        .update_in(cx, |pane, window, cx| {
23702            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23703        })
23704        .await
23705        .unwrap();
23706    drop(editor_1);
23707    pane_1.update(cx, |pane, cx| {
23708        pane.active_item()
23709            .unwrap()
23710            .downcast::<Editor>()
23711            .unwrap()
23712            .update(cx, |editor, cx| {
23713                assert_eq!(
23714                    editor.display_text(cx),
23715                    lib_text,
23716                    "Other file should be open and active",
23717                );
23718            });
23719        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23720    });
23721
23722    let _other_editor_2 = workspace
23723        .update_in(cx, |workspace, window, cx| {
23724            workspace.open_path(
23725                (worktree_id, rel_path("lib.rs")),
23726                Some(pane_2.downgrade()),
23727                true,
23728                window,
23729                cx,
23730            )
23731        })
23732        .unwrap()
23733        .await
23734        .downcast::<Editor>()
23735        .unwrap();
23736    pane_2
23737        .update_in(cx, |pane, window, cx| {
23738            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23739        })
23740        .await
23741        .unwrap();
23742    drop(editor_2);
23743    pane_2.update(cx, |pane, cx| {
23744        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23745        open_editor.update(cx, |editor, cx| {
23746            assert_eq!(
23747                editor.display_text(cx),
23748                lib_text,
23749                "Other file should be open and active in another panel too",
23750            );
23751        });
23752        assert_eq!(
23753            pane.items().count(),
23754            1,
23755            "No other editors should be open in another pane",
23756        );
23757    });
23758
23759    let _editor_1_reopened = workspace
23760        .update_in(cx, |workspace, window, cx| {
23761            workspace.open_path(
23762                (worktree_id, rel_path("main.rs")),
23763                Some(pane_1.downgrade()),
23764                true,
23765                window,
23766                cx,
23767            )
23768        })
23769        .unwrap()
23770        .await
23771        .downcast::<Editor>()
23772        .unwrap();
23773    let _editor_2_reopened = workspace
23774        .update_in(cx, |workspace, window, cx| {
23775            workspace.open_path(
23776                (worktree_id, rel_path("main.rs")),
23777                Some(pane_2.downgrade()),
23778                true,
23779                window,
23780                cx,
23781            )
23782        })
23783        .unwrap()
23784        .await
23785        .downcast::<Editor>()
23786        .unwrap();
23787    pane_1.update(cx, |pane, cx| {
23788        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23789        open_editor.update(cx, |editor, cx| {
23790            assert_eq!(
23791                editor.display_text(cx),
23792                main_text,
23793                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23794            );
23795            assert_eq!(
23796                editor
23797                    .selections
23798                    .all::<Point>(cx)
23799                    .into_iter()
23800                    .map(|s| s.range())
23801                    .collect::<Vec<_>>(),
23802                expected_ranges,
23803                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23804            );
23805        })
23806    });
23807    pane_2.update(cx, |pane, cx| {
23808        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23809        open_editor.update(cx, |editor, cx| {
23810            assert_eq!(
23811                editor.display_text(cx),
23812                r#"fn main() {
23813⋯rintln!("1");
23814⋯intln!("2");
23815⋯ntln!("3");
23816println!("4");
23817println!("5");
23818}"#,
23819                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23820            );
23821            assert_eq!(
23822                editor
23823                    .selections
23824                    .all::<Point>(cx)
23825                    .into_iter()
23826                    .map(|s| s.range())
23827                    .collect::<Vec<_>>(),
23828                vec![Point::zero()..Point::zero()],
23829                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23830            );
23831        })
23832    });
23833}
23834
23835#[gpui::test]
23836async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23837    init_test(cx, |_| {});
23838
23839    let fs = FakeFs::new(cx.executor());
23840    let main_text = r#"fn main() {
23841println!("1");
23842println!("2");
23843println!("3");
23844println!("4");
23845println!("5");
23846}"#;
23847    let lib_text = "mod foo {}";
23848    fs.insert_tree(
23849        path!("/a"),
23850        json!({
23851            "lib.rs": lib_text,
23852            "main.rs": main_text,
23853        }),
23854    )
23855    .await;
23856
23857    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23858    let (workspace, cx) =
23859        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23860    let worktree_id = workspace.update(cx, |workspace, cx| {
23861        workspace.project().update(cx, |project, cx| {
23862            project.worktrees(cx).next().unwrap().read(cx).id()
23863        })
23864    });
23865
23866    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23867    let editor = workspace
23868        .update_in(cx, |workspace, window, cx| {
23869            workspace.open_path(
23870                (worktree_id, rel_path("main.rs")),
23871                Some(pane.downgrade()),
23872                true,
23873                window,
23874                cx,
23875            )
23876        })
23877        .unwrap()
23878        .await
23879        .downcast::<Editor>()
23880        .unwrap();
23881    pane.update(cx, |pane, cx| {
23882        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23883        open_editor.update(cx, |editor, cx| {
23884            assert_eq!(
23885                editor.display_text(cx),
23886                main_text,
23887                "Original main.rs text on initial open",
23888            );
23889        })
23890    });
23891    editor.update_in(cx, |editor, window, cx| {
23892        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23893    });
23894
23895    cx.update_global(|store: &mut SettingsStore, cx| {
23896        store.update_user_settings(cx, |s| {
23897            s.workspace.restore_on_file_reopen = Some(false);
23898        });
23899    });
23900    editor.update_in(cx, |editor, window, cx| {
23901        editor.fold_ranges(
23902            vec![
23903                Point::new(1, 0)..Point::new(1, 1),
23904                Point::new(2, 0)..Point::new(2, 2),
23905                Point::new(3, 0)..Point::new(3, 3),
23906            ],
23907            false,
23908            window,
23909            cx,
23910        );
23911    });
23912    pane.update_in(cx, |pane, window, cx| {
23913        pane.close_all_items(&CloseAllItems::default(), window, cx)
23914    })
23915    .await
23916    .unwrap();
23917    pane.update(cx, |pane, _| {
23918        assert!(pane.active_item().is_none());
23919    });
23920    cx.update_global(|store: &mut SettingsStore, cx| {
23921        store.update_user_settings(cx, |s| {
23922            s.workspace.restore_on_file_reopen = Some(true);
23923        });
23924    });
23925
23926    let _editor_reopened = workspace
23927        .update_in(cx, |workspace, window, cx| {
23928            workspace.open_path(
23929                (worktree_id, rel_path("main.rs")),
23930                Some(pane.downgrade()),
23931                true,
23932                window,
23933                cx,
23934            )
23935        })
23936        .unwrap()
23937        .await
23938        .downcast::<Editor>()
23939        .unwrap();
23940    pane.update(cx, |pane, cx| {
23941        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23942        open_editor.update(cx, |editor, cx| {
23943            assert_eq!(
23944                editor.display_text(cx),
23945                main_text,
23946                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23947            );
23948        })
23949    });
23950}
23951
23952#[gpui::test]
23953async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23954    struct EmptyModalView {
23955        focus_handle: gpui::FocusHandle,
23956    }
23957    impl EventEmitter<DismissEvent> for EmptyModalView {}
23958    impl Render for EmptyModalView {
23959        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23960            div()
23961        }
23962    }
23963    impl Focusable for EmptyModalView {
23964        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23965            self.focus_handle.clone()
23966        }
23967    }
23968    impl workspace::ModalView for EmptyModalView {}
23969    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23970        EmptyModalView {
23971            focus_handle: cx.focus_handle(),
23972        }
23973    }
23974
23975    init_test(cx, |_| {});
23976
23977    let fs = FakeFs::new(cx.executor());
23978    let project = Project::test(fs, [], cx).await;
23979    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23980    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23981    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23982    let editor = cx.new_window_entity(|window, cx| {
23983        Editor::new(
23984            EditorMode::full(),
23985            buffer,
23986            Some(project.clone()),
23987            window,
23988            cx,
23989        )
23990    });
23991    workspace
23992        .update(cx, |workspace, window, cx| {
23993            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23994        })
23995        .unwrap();
23996    editor.update_in(cx, |editor, window, cx| {
23997        editor.open_context_menu(&OpenContextMenu, window, cx);
23998        assert!(editor.mouse_context_menu.is_some());
23999    });
24000    workspace
24001        .update(cx, |workspace, window, cx| {
24002            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24003        })
24004        .unwrap();
24005    cx.read(|cx| {
24006        assert!(editor.read(cx).mouse_context_menu.is_none());
24007    });
24008}
24009
24010fn set_linked_edit_ranges(
24011    opening: (Point, Point),
24012    closing: (Point, Point),
24013    editor: &mut Editor,
24014    cx: &mut Context<Editor>,
24015) {
24016    let Some((buffer, _)) = editor
24017        .buffer
24018        .read(cx)
24019        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24020    else {
24021        panic!("Failed to get buffer for selection position");
24022    };
24023    let buffer = buffer.read(cx);
24024    let buffer_id = buffer.remote_id();
24025    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24026    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24027    let mut linked_ranges = HashMap::default();
24028    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24029    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24030}
24031
24032#[gpui::test]
24033async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24034    init_test(cx, |_| {});
24035
24036    let fs = FakeFs::new(cx.executor());
24037    fs.insert_file(path!("/file.html"), Default::default())
24038        .await;
24039
24040    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24041
24042    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24043    let html_language = Arc::new(Language::new(
24044        LanguageConfig {
24045            name: "HTML".into(),
24046            matcher: LanguageMatcher {
24047                path_suffixes: vec!["html".to_string()],
24048                ..LanguageMatcher::default()
24049            },
24050            brackets: BracketPairConfig {
24051                pairs: vec![BracketPair {
24052                    start: "<".into(),
24053                    end: ">".into(),
24054                    close: true,
24055                    ..Default::default()
24056                }],
24057                ..Default::default()
24058            },
24059            ..Default::default()
24060        },
24061        Some(tree_sitter_html::LANGUAGE.into()),
24062    ));
24063    language_registry.add(html_language);
24064    let mut fake_servers = language_registry.register_fake_lsp(
24065        "HTML",
24066        FakeLspAdapter {
24067            capabilities: lsp::ServerCapabilities {
24068                completion_provider: Some(lsp::CompletionOptions {
24069                    resolve_provider: Some(true),
24070                    ..Default::default()
24071                }),
24072                ..Default::default()
24073            },
24074            ..Default::default()
24075        },
24076    );
24077
24078    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24079    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24080
24081    let worktree_id = workspace
24082        .update(cx, |workspace, _window, cx| {
24083            workspace.project().update(cx, |project, cx| {
24084                project.worktrees(cx).next().unwrap().read(cx).id()
24085            })
24086        })
24087        .unwrap();
24088    project
24089        .update(cx, |project, cx| {
24090            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24091        })
24092        .await
24093        .unwrap();
24094    let editor = workspace
24095        .update(cx, |workspace, window, cx| {
24096            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24097        })
24098        .unwrap()
24099        .await
24100        .unwrap()
24101        .downcast::<Editor>()
24102        .unwrap();
24103
24104    let fake_server = fake_servers.next().await.unwrap();
24105    editor.update_in(cx, |editor, window, cx| {
24106        editor.set_text("<ad></ad>", window, cx);
24107        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24108            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24109        });
24110        set_linked_edit_ranges(
24111            (Point::new(0, 1), Point::new(0, 3)),
24112            (Point::new(0, 6), Point::new(0, 8)),
24113            editor,
24114            cx,
24115        );
24116    });
24117    let mut completion_handle =
24118        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24119            Ok(Some(lsp::CompletionResponse::Array(vec![
24120                lsp::CompletionItem {
24121                    label: "head".to_string(),
24122                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24123                        lsp::InsertReplaceEdit {
24124                            new_text: "head".to_string(),
24125                            insert: lsp::Range::new(
24126                                lsp::Position::new(0, 1),
24127                                lsp::Position::new(0, 3),
24128                            ),
24129                            replace: lsp::Range::new(
24130                                lsp::Position::new(0, 1),
24131                                lsp::Position::new(0, 3),
24132                            ),
24133                        },
24134                    )),
24135                    ..Default::default()
24136                },
24137            ])))
24138        });
24139    editor.update_in(cx, |editor, window, cx| {
24140        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24141    });
24142    cx.run_until_parked();
24143    completion_handle.next().await.unwrap();
24144    editor.update(cx, |editor, _| {
24145        assert!(
24146            editor.context_menu_visible(),
24147            "Completion menu should be visible"
24148        );
24149    });
24150    editor.update_in(cx, |editor, window, cx| {
24151        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24152    });
24153    cx.executor().run_until_parked();
24154    editor.update(cx, |editor, cx| {
24155        assert_eq!(editor.text(cx), "<head></head>");
24156    });
24157}
24158
24159#[gpui::test]
24160async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24161    init_test(cx, |_| {});
24162
24163    let mut cx = EditorTestContext::new(cx).await;
24164    let language = Arc::new(Language::new(
24165        LanguageConfig {
24166            name: "TSX".into(),
24167            matcher: LanguageMatcher {
24168                path_suffixes: vec!["tsx".to_string()],
24169                ..LanguageMatcher::default()
24170            },
24171            brackets: BracketPairConfig {
24172                pairs: vec![BracketPair {
24173                    start: "<".into(),
24174                    end: ">".into(),
24175                    close: true,
24176                    ..Default::default()
24177                }],
24178                ..Default::default()
24179            },
24180            linked_edit_characters: HashSet::from_iter(['.']),
24181            ..Default::default()
24182        },
24183        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24184    ));
24185    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24186
24187    // Test typing > does not extend linked pair
24188    cx.set_state("<divˇ<div></div>");
24189    cx.update_editor(|editor, _, cx| {
24190        set_linked_edit_ranges(
24191            (Point::new(0, 1), Point::new(0, 4)),
24192            (Point::new(0, 11), Point::new(0, 14)),
24193            editor,
24194            cx,
24195        );
24196    });
24197    cx.update_editor(|editor, window, cx| {
24198        editor.handle_input(">", window, cx);
24199    });
24200    cx.assert_editor_state("<div>ˇ<div></div>");
24201
24202    // Test typing . do extend linked pair
24203    cx.set_state("<Animatedˇ></Animated>");
24204    cx.update_editor(|editor, _, cx| {
24205        set_linked_edit_ranges(
24206            (Point::new(0, 1), Point::new(0, 9)),
24207            (Point::new(0, 12), Point::new(0, 20)),
24208            editor,
24209            cx,
24210        );
24211    });
24212    cx.update_editor(|editor, window, cx| {
24213        editor.handle_input(".", window, cx);
24214    });
24215    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24216    cx.update_editor(|editor, _, cx| {
24217        set_linked_edit_ranges(
24218            (Point::new(0, 1), Point::new(0, 10)),
24219            (Point::new(0, 13), Point::new(0, 21)),
24220            editor,
24221            cx,
24222        );
24223    });
24224    cx.update_editor(|editor, window, cx| {
24225        editor.handle_input("V", window, cx);
24226    });
24227    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24228}
24229
24230#[gpui::test]
24231async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24232    init_test(cx, |_| {});
24233
24234    let fs = FakeFs::new(cx.executor());
24235    fs.insert_tree(
24236        path!("/root"),
24237        json!({
24238            "a": {
24239                "main.rs": "fn main() {}",
24240            },
24241            "foo": {
24242                "bar": {
24243                    "external_file.rs": "pub mod external {}",
24244                }
24245            }
24246        }),
24247    )
24248    .await;
24249
24250    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24251    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24252    language_registry.add(rust_lang());
24253    let _fake_servers = language_registry.register_fake_lsp(
24254        "Rust",
24255        FakeLspAdapter {
24256            ..FakeLspAdapter::default()
24257        },
24258    );
24259    let (workspace, cx) =
24260        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24261    let worktree_id = workspace.update(cx, |workspace, cx| {
24262        workspace.project().update(cx, |project, cx| {
24263            project.worktrees(cx).next().unwrap().read(cx).id()
24264        })
24265    });
24266
24267    let assert_language_servers_count =
24268        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24269            project.update(cx, |project, cx| {
24270                let current = project
24271                    .lsp_store()
24272                    .read(cx)
24273                    .as_local()
24274                    .unwrap()
24275                    .language_servers
24276                    .len();
24277                assert_eq!(expected, current, "{context}");
24278            });
24279        };
24280
24281    assert_language_servers_count(
24282        0,
24283        "No servers should be running before any file is open",
24284        cx,
24285    );
24286    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24287    let main_editor = workspace
24288        .update_in(cx, |workspace, window, cx| {
24289            workspace.open_path(
24290                (worktree_id, rel_path("main.rs")),
24291                Some(pane.downgrade()),
24292                true,
24293                window,
24294                cx,
24295            )
24296        })
24297        .unwrap()
24298        .await
24299        .downcast::<Editor>()
24300        .unwrap();
24301    pane.update(cx, |pane, cx| {
24302        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24303        open_editor.update(cx, |editor, cx| {
24304            assert_eq!(
24305                editor.display_text(cx),
24306                "fn main() {}",
24307                "Original main.rs text on initial open",
24308            );
24309        });
24310        assert_eq!(open_editor, main_editor);
24311    });
24312    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24313
24314    let external_editor = workspace
24315        .update_in(cx, |workspace, window, cx| {
24316            workspace.open_abs_path(
24317                PathBuf::from("/root/foo/bar/external_file.rs"),
24318                OpenOptions::default(),
24319                window,
24320                cx,
24321            )
24322        })
24323        .await
24324        .expect("opening external file")
24325        .downcast::<Editor>()
24326        .expect("downcasted external file's open element to editor");
24327    pane.update(cx, |pane, cx| {
24328        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24329        open_editor.update(cx, |editor, cx| {
24330            assert_eq!(
24331                editor.display_text(cx),
24332                "pub mod external {}",
24333                "External file is open now",
24334            );
24335        });
24336        assert_eq!(open_editor, external_editor);
24337    });
24338    assert_language_servers_count(
24339        1,
24340        "Second, external, *.rs file should join the existing server",
24341        cx,
24342    );
24343
24344    pane.update_in(cx, |pane, window, cx| {
24345        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24346    })
24347    .await
24348    .unwrap();
24349    pane.update_in(cx, |pane, window, cx| {
24350        pane.navigate_backward(&Default::default(), window, cx);
24351    });
24352    cx.run_until_parked();
24353    pane.update(cx, |pane, cx| {
24354        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24355        open_editor.update(cx, |editor, cx| {
24356            assert_eq!(
24357                editor.display_text(cx),
24358                "pub mod external {}",
24359                "External file is open now",
24360            );
24361        });
24362    });
24363    assert_language_servers_count(
24364        1,
24365        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24366        cx,
24367    );
24368
24369    cx.update(|_, cx| {
24370        workspace::reload(cx);
24371    });
24372    assert_language_servers_count(
24373        1,
24374        "After reloading the worktree with local and external files opened, only one project should be started",
24375        cx,
24376    );
24377}
24378
24379#[gpui::test]
24380async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24381    init_test(cx, |_| {});
24382
24383    let mut cx = EditorTestContext::new(cx).await;
24384    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24385    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24386
24387    // test cursor move to start of each line on tab
24388    // for `if`, `elif`, `else`, `while`, `with` and `for`
24389    cx.set_state(indoc! {"
24390        def main():
24391        ˇ    for item in items:
24392        ˇ        while item.active:
24393        ˇ            if item.value > 10:
24394        ˇ                continue
24395        ˇ            elif item.value < 0:
24396        ˇ                break
24397        ˇ            else:
24398        ˇ                with item.context() as ctx:
24399        ˇ                    yield count
24400        ˇ        else:
24401        ˇ            log('while else')
24402        ˇ    else:
24403        ˇ        log('for else')
24404    "});
24405    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24406    cx.assert_editor_state(indoc! {"
24407        def main():
24408            ˇfor item in items:
24409                ˇwhile item.active:
24410                    ˇif item.value > 10:
24411                        ˇcontinue
24412                    ˇelif item.value < 0:
24413                        ˇbreak
24414                    ˇelse:
24415                        ˇwith item.context() as ctx:
24416                            ˇyield count
24417                ˇelse:
24418                    ˇlog('while else')
24419            ˇelse:
24420                ˇlog('for else')
24421    "});
24422    // test relative indent is preserved when tab
24423    // for `if`, `elif`, `else`, `while`, `with` and `for`
24424    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24425    cx.assert_editor_state(indoc! {"
24426        def main():
24427                ˇfor item in items:
24428                    ˇwhile item.active:
24429                        ˇif item.value > 10:
24430                            ˇcontinue
24431                        ˇelif item.value < 0:
24432                            ˇbreak
24433                        ˇelse:
24434                            ˇwith item.context() as ctx:
24435                                ˇyield count
24436                    ˇelse:
24437                        ˇlog('while else')
24438                ˇelse:
24439                    ˇlog('for else')
24440    "});
24441
24442    // test cursor move to start of each line on tab
24443    // for `try`, `except`, `else`, `finally`, `match` and `def`
24444    cx.set_state(indoc! {"
24445        def main():
24446        ˇ    try:
24447        ˇ        fetch()
24448        ˇ    except ValueError:
24449        ˇ        handle_error()
24450        ˇ    else:
24451        ˇ        match value:
24452        ˇ            case _:
24453        ˇ    finally:
24454        ˇ        def status():
24455        ˇ            return 0
24456    "});
24457    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24458    cx.assert_editor_state(indoc! {"
24459        def main():
24460            ˇtry:
24461                ˇfetch()
24462            ˇexcept ValueError:
24463                ˇhandle_error()
24464            ˇelse:
24465                ˇmatch value:
24466                    ˇcase _:
24467            ˇfinally:
24468                ˇdef status():
24469                    ˇreturn 0
24470    "});
24471    // test relative indent is preserved when tab
24472    // for `try`, `except`, `else`, `finally`, `match` and `def`
24473    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24474    cx.assert_editor_state(indoc! {"
24475        def main():
24476                ˇtry:
24477                    ˇfetch()
24478                ˇexcept ValueError:
24479                    ˇhandle_error()
24480                ˇelse:
24481                    ˇmatch value:
24482                        ˇcase _:
24483                ˇfinally:
24484                    ˇdef status():
24485                        ˇreturn 0
24486    "});
24487}
24488
24489#[gpui::test]
24490async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24491    init_test(cx, |_| {});
24492
24493    let mut cx = EditorTestContext::new(cx).await;
24494    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24495    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24496
24497    // test `else` auto outdents when typed inside `if` block
24498    cx.set_state(indoc! {"
24499        def main():
24500            if i == 2:
24501                return
24502                ˇ
24503    "});
24504    cx.update_editor(|editor, window, cx| {
24505        editor.handle_input("else:", window, cx);
24506    });
24507    cx.assert_editor_state(indoc! {"
24508        def main():
24509            if i == 2:
24510                return
24511            else:ˇ
24512    "});
24513
24514    // test `except` auto outdents when typed inside `try` block
24515    cx.set_state(indoc! {"
24516        def main():
24517            try:
24518                i = 2
24519                ˇ
24520    "});
24521    cx.update_editor(|editor, window, cx| {
24522        editor.handle_input("except:", window, cx);
24523    });
24524    cx.assert_editor_state(indoc! {"
24525        def main():
24526            try:
24527                i = 2
24528            except:ˇ
24529    "});
24530
24531    // test `else` auto outdents when typed inside `except` block
24532    cx.set_state(indoc! {"
24533        def main():
24534            try:
24535                i = 2
24536            except:
24537                j = 2
24538                ˇ
24539    "});
24540    cx.update_editor(|editor, window, cx| {
24541        editor.handle_input("else:", window, cx);
24542    });
24543    cx.assert_editor_state(indoc! {"
24544        def main():
24545            try:
24546                i = 2
24547            except:
24548                j = 2
24549            else:ˇ
24550    "});
24551
24552    // test `finally` auto outdents when typed inside `else` block
24553    cx.set_state(indoc! {"
24554        def main():
24555            try:
24556                i = 2
24557            except:
24558                j = 2
24559            else:
24560                k = 2
24561                ˇ
24562    "});
24563    cx.update_editor(|editor, window, cx| {
24564        editor.handle_input("finally:", window, cx);
24565    });
24566    cx.assert_editor_state(indoc! {"
24567        def main():
24568            try:
24569                i = 2
24570            except:
24571                j = 2
24572            else:
24573                k = 2
24574            finally:ˇ
24575    "});
24576
24577    // test `else` does not outdents when typed inside `except` block right after for block
24578    cx.set_state(indoc! {"
24579        def main():
24580            try:
24581                i = 2
24582            except:
24583                for i in range(n):
24584                    pass
24585                ˇ
24586    "});
24587    cx.update_editor(|editor, window, cx| {
24588        editor.handle_input("else:", window, cx);
24589    });
24590    cx.assert_editor_state(indoc! {"
24591        def main():
24592            try:
24593                i = 2
24594            except:
24595                for i in range(n):
24596                    pass
24597                else:ˇ
24598    "});
24599
24600    // test `finally` auto outdents when typed inside `else` block right after for block
24601    cx.set_state(indoc! {"
24602        def main():
24603            try:
24604                i = 2
24605            except:
24606                j = 2
24607            else:
24608                for i in range(n):
24609                    pass
24610                ˇ
24611    "});
24612    cx.update_editor(|editor, window, cx| {
24613        editor.handle_input("finally:", window, cx);
24614    });
24615    cx.assert_editor_state(indoc! {"
24616        def main():
24617            try:
24618                i = 2
24619            except:
24620                j = 2
24621            else:
24622                for i in range(n):
24623                    pass
24624            finally:ˇ
24625    "});
24626
24627    // test `except` outdents to inner "try" block
24628    cx.set_state(indoc! {"
24629        def main():
24630            try:
24631                i = 2
24632                if i == 2:
24633                    try:
24634                        i = 3
24635                        ˇ
24636    "});
24637    cx.update_editor(|editor, window, cx| {
24638        editor.handle_input("except:", window, cx);
24639    });
24640    cx.assert_editor_state(indoc! {"
24641        def main():
24642            try:
24643                i = 2
24644                if i == 2:
24645                    try:
24646                        i = 3
24647                    except:ˇ
24648    "});
24649
24650    // test `except` outdents to outer "try" block
24651    cx.set_state(indoc! {"
24652        def main():
24653            try:
24654                i = 2
24655                if i == 2:
24656                    try:
24657                        i = 3
24658                ˇ
24659    "});
24660    cx.update_editor(|editor, window, cx| {
24661        editor.handle_input("except:", window, cx);
24662    });
24663    cx.assert_editor_state(indoc! {"
24664        def main():
24665            try:
24666                i = 2
24667                if i == 2:
24668                    try:
24669                        i = 3
24670            except:ˇ
24671    "});
24672
24673    // test `else` stays at correct indent when typed after `for` block
24674    cx.set_state(indoc! {"
24675        def main():
24676            for i in range(10):
24677                if i == 3:
24678                    break
24679            ˇ
24680    "});
24681    cx.update_editor(|editor, window, cx| {
24682        editor.handle_input("else:", window, cx);
24683    });
24684    cx.assert_editor_state(indoc! {"
24685        def main():
24686            for i in range(10):
24687                if i == 3:
24688                    break
24689            else:ˇ
24690    "});
24691
24692    // test does not outdent on typing after line with square brackets
24693    cx.set_state(indoc! {"
24694        def f() -> list[str]:
24695            ˇ
24696    "});
24697    cx.update_editor(|editor, window, cx| {
24698        editor.handle_input("a", window, cx);
24699    });
24700    cx.assert_editor_state(indoc! {"
24701        def f() -> list[str]:
2470224703    "});
24704
24705    // test does not outdent on typing : after case keyword
24706    cx.set_state(indoc! {"
24707        match 1:
24708            caseˇ
24709    "});
24710    cx.update_editor(|editor, window, cx| {
24711        editor.handle_input(":", window, cx);
24712    });
24713    cx.assert_editor_state(indoc! {"
24714        match 1:
24715            case:ˇ
24716    "});
24717}
24718
24719#[gpui::test]
24720async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24721    init_test(cx, |_| {});
24722    update_test_language_settings(cx, |settings| {
24723        settings.defaults.extend_comment_on_newline = Some(false);
24724    });
24725    let mut cx = EditorTestContext::new(cx).await;
24726    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24727    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24728
24729    // test correct indent after newline on comment
24730    cx.set_state(indoc! {"
24731        # COMMENT:ˇ
24732    "});
24733    cx.update_editor(|editor, window, cx| {
24734        editor.newline(&Newline, window, cx);
24735    });
24736    cx.assert_editor_state(indoc! {"
24737        # COMMENT:
24738        ˇ
24739    "});
24740
24741    // test correct indent after newline in brackets
24742    cx.set_state(indoc! {"
24743        {ˇ}
24744    "});
24745    cx.update_editor(|editor, window, cx| {
24746        editor.newline(&Newline, window, cx);
24747    });
24748    cx.run_until_parked();
24749    cx.assert_editor_state(indoc! {"
24750        {
24751            ˇ
24752        }
24753    "});
24754
24755    cx.set_state(indoc! {"
24756        (ˇ)
24757    "});
24758    cx.update_editor(|editor, window, cx| {
24759        editor.newline(&Newline, window, cx);
24760    });
24761    cx.run_until_parked();
24762    cx.assert_editor_state(indoc! {"
24763        (
24764            ˇ
24765        )
24766    "});
24767
24768    // do not indent after empty lists or dictionaries
24769    cx.set_state(indoc! {"
24770        a = []ˇ
24771    "});
24772    cx.update_editor(|editor, window, cx| {
24773        editor.newline(&Newline, window, cx);
24774    });
24775    cx.run_until_parked();
24776    cx.assert_editor_state(indoc! {"
24777        a = []
24778        ˇ
24779    "});
24780}
24781
24782#[gpui::test]
24783async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24784    init_test(cx, |_| {});
24785
24786    let mut cx = EditorTestContext::new(cx).await;
24787    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24788    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24789
24790    // test cursor move to start of each line on tab
24791    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24792    cx.set_state(indoc! {"
24793        function main() {
24794        ˇ    for item in $items; do
24795        ˇ        while [ -n \"$item\" ]; do
24796        ˇ            if [ \"$value\" -gt 10 ]; then
24797        ˇ                continue
24798        ˇ            elif [ \"$value\" -lt 0 ]; then
24799        ˇ                break
24800        ˇ            else
24801        ˇ                echo \"$item\"
24802        ˇ            fi
24803        ˇ        done
24804        ˇ    done
24805        ˇ}
24806    "});
24807    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24808    cx.assert_editor_state(indoc! {"
24809        function main() {
24810            ˇfor item in $items; do
24811                ˇwhile [ -n \"$item\" ]; do
24812                    ˇif [ \"$value\" -gt 10 ]; then
24813                        ˇcontinue
24814                    ˇelif [ \"$value\" -lt 0 ]; then
24815                        ˇbreak
24816                    ˇelse
24817                        ˇecho \"$item\"
24818                    ˇfi
24819                ˇdone
24820            ˇdone
24821        ˇ}
24822    "});
24823    // test relative indent is preserved when tab
24824    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24825    cx.assert_editor_state(indoc! {"
24826        function main() {
24827                ˇfor item in $items; do
24828                    ˇwhile [ -n \"$item\" ]; do
24829                        ˇif [ \"$value\" -gt 10 ]; then
24830                            ˇcontinue
24831                        ˇelif [ \"$value\" -lt 0 ]; then
24832                            ˇbreak
24833                        ˇelse
24834                            ˇecho \"$item\"
24835                        ˇfi
24836                    ˇdone
24837                ˇdone
24838            ˇ}
24839    "});
24840
24841    // test cursor move to start of each line on tab
24842    // for `case` statement with patterns
24843    cx.set_state(indoc! {"
24844        function handle() {
24845        ˇ    case \"$1\" in
24846        ˇ        start)
24847        ˇ            echo \"a\"
24848        ˇ            ;;
24849        ˇ        stop)
24850        ˇ            echo \"b\"
24851        ˇ            ;;
24852        ˇ        *)
24853        ˇ            echo \"c\"
24854        ˇ            ;;
24855        ˇ    esac
24856        ˇ}
24857    "});
24858    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24859    cx.assert_editor_state(indoc! {"
24860        function handle() {
24861            ˇcase \"$1\" in
24862                ˇstart)
24863                    ˇecho \"a\"
24864                    ˇ;;
24865                ˇstop)
24866                    ˇecho \"b\"
24867                    ˇ;;
24868                ˇ*)
24869                    ˇecho \"c\"
24870                    ˇ;;
24871            ˇesac
24872        ˇ}
24873    "});
24874}
24875
24876#[gpui::test]
24877async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24878    init_test(cx, |_| {});
24879
24880    let mut cx = EditorTestContext::new(cx).await;
24881    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24882    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24883
24884    // test indents on comment insert
24885    cx.set_state(indoc! {"
24886        function main() {
24887        ˇ    for item in $items; do
24888        ˇ        while [ -n \"$item\" ]; do
24889        ˇ            if [ \"$value\" -gt 10 ]; then
24890        ˇ                continue
24891        ˇ            elif [ \"$value\" -lt 0 ]; then
24892        ˇ                break
24893        ˇ            else
24894        ˇ                echo \"$item\"
24895        ˇ            fi
24896        ˇ        done
24897        ˇ    done
24898        ˇ}
24899    "});
24900    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24901    cx.assert_editor_state(indoc! {"
24902        function main() {
24903        #ˇ    for item in $items; do
24904        #ˇ        while [ -n \"$item\" ]; do
24905        #ˇ            if [ \"$value\" -gt 10 ]; then
24906        #ˇ                continue
24907        #ˇ            elif [ \"$value\" -lt 0 ]; then
24908        #ˇ                break
24909        #ˇ            else
24910        #ˇ                echo \"$item\"
24911        #ˇ            fi
24912        #ˇ        done
24913        #ˇ    done
24914        #ˇ}
24915    "});
24916}
24917
24918#[gpui::test]
24919async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24920    init_test(cx, |_| {});
24921
24922    let mut cx = EditorTestContext::new(cx).await;
24923    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24924    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24925
24926    // test `else` auto outdents when typed inside `if` block
24927    cx.set_state(indoc! {"
24928        if [ \"$1\" = \"test\" ]; then
24929            echo \"foo bar\"
24930            ˇ
24931    "});
24932    cx.update_editor(|editor, window, cx| {
24933        editor.handle_input("else", window, cx);
24934    });
24935    cx.assert_editor_state(indoc! {"
24936        if [ \"$1\" = \"test\" ]; then
24937            echo \"foo bar\"
24938        elseˇ
24939    "});
24940
24941    // test `elif` auto outdents when typed inside `if` block
24942    cx.set_state(indoc! {"
24943        if [ \"$1\" = \"test\" ]; then
24944            echo \"foo bar\"
24945            ˇ
24946    "});
24947    cx.update_editor(|editor, window, cx| {
24948        editor.handle_input("elif", window, cx);
24949    });
24950    cx.assert_editor_state(indoc! {"
24951        if [ \"$1\" = \"test\" ]; then
24952            echo \"foo bar\"
24953        elifˇ
24954    "});
24955
24956    // test `fi` auto outdents when typed inside `else` block
24957    cx.set_state(indoc! {"
24958        if [ \"$1\" = \"test\" ]; then
24959            echo \"foo bar\"
24960        else
24961            echo \"bar baz\"
24962            ˇ
24963    "});
24964    cx.update_editor(|editor, window, cx| {
24965        editor.handle_input("fi", window, cx);
24966    });
24967    cx.assert_editor_state(indoc! {"
24968        if [ \"$1\" = \"test\" ]; then
24969            echo \"foo bar\"
24970        else
24971            echo \"bar baz\"
24972        fiˇ
24973    "});
24974
24975    // test `done` auto outdents when typed inside `while` block
24976    cx.set_state(indoc! {"
24977        while read line; do
24978            echo \"$line\"
24979            ˇ
24980    "});
24981    cx.update_editor(|editor, window, cx| {
24982        editor.handle_input("done", window, cx);
24983    });
24984    cx.assert_editor_state(indoc! {"
24985        while read line; do
24986            echo \"$line\"
24987        doneˇ
24988    "});
24989
24990    // test `done` auto outdents when typed inside `for` block
24991    cx.set_state(indoc! {"
24992        for file in *.txt; do
24993            cat \"$file\"
24994            ˇ
24995    "});
24996    cx.update_editor(|editor, window, cx| {
24997        editor.handle_input("done", window, cx);
24998    });
24999    cx.assert_editor_state(indoc! {"
25000        for file in *.txt; do
25001            cat \"$file\"
25002        doneˇ
25003    "});
25004
25005    // test `esac` auto outdents when typed inside `case` block
25006    cx.set_state(indoc! {"
25007        case \"$1\" in
25008            start)
25009                echo \"foo bar\"
25010                ;;
25011            stop)
25012                echo \"bar baz\"
25013                ;;
25014            ˇ
25015    "});
25016    cx.update_editor(|editor, window, cx| {
25017        editor.handle_input("esac", window, cx);
25018    });
25019    cx.assert_editor_state(indoc! {"
25020        case \"$1\" in
25021            start)
25022                echo \"foo bar\"
25023                ;;
25024            stop)
25025                echo \"bar baz\"
25026                ;;
25027        esacˇ
25028    "});
25029
25030    // test `*)` auto outdents when typed inside `case` block
25031    cx.set_state(indoc! {"
25032        case \"$1\" in
25033            start)
25034                echo \"foo bar\"
25035                ;;
25036                ˇ
25037    "});
25038    cx.update_editor(|editor, window, cx| {
25039        editor.handle_input("*)", window, cx);
25040    });
25041    cx.assert_editor_state(indoc! {"
25042        case \"$1\" in
25043            start)
25044                echo \"foo bar\"
25045                ;;
25046            *)ˇ
25047    "});
25048
25049    // test `fi` outdents to correct level with nested if blocks
25050    cx.set_state(indoc! {"
25051        if [ \"$1\" = \"test\" ]; then
25052            echo \"outer if\"
25053            if [ \"$2\" = \"debug\" ]; then
25054                echo \"inner if\"
25055                ˇ
25056    "});
25057    cx.update_editor(|editor, window, cx| {
25058        editor.handle_input("fi", window, cx);
25059    });
25060    cx.assert_editor_state(indoc! {"
25061        if [ \"$1\" = \"test\" ]; then
25062            echo \"outer if\"
25063            if [ \"$2\" = \"debug\" ]; then
25064                echo \"inner if\"
25065            fiˇ
25066    "});
25067}
25068
25069#[gpui::test]
25070async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25071    init_test(cx, |_| {});
25072    update_test_language_settings(cx, |settings| {
25073        settings.defaults.extend_comment_on_newline = Some(false);
25074    });
25075    let mut cx = EditorTestContext::new(cx).await;
25076    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25077    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25078
25079    // test correct indent after newline on comment
25080    cx.set_state(indoc! {"
25081        # COMMENT:ˇ
25082    "});
25083    cx.update_editor(|editor, window, cx| {
25084        editor.newline(&Newline, window, cx);
25085    });
25086    cx.assert_editor_state(indoc! {"
25087        # COMMENT:
25088        ˇ
25089    "});
25090
25091    // test correct indent after newline after `then`
25092    cx.set_state(indoc! {"
25093
25094        if [ \"$1\" = \"test\" ]; thenˇ
25095    "});
25096    cx.update_editor(|editor, window, cx| {
25097        editor.newline(&Newline, window, cx);
25098    });
25099    cx.run_until_parked();
25100    cx.assert_editor_state(indoc! {"
25101
25102        if [ \"$1\" = \"test\" ]; then
25103            ˇ
25104    "});
25105
25106    // test correct indent after newline after `else`
25107    cx.set_state(indoc! {"
25108        if [ \"$1\" = \"test\" ]; then
25109        elseˇ
25110    "});
25111    cx.update_editor(|editor, window, cx| {
25112        editor.newline(&Newline, window, cx);
25113    });
25114    cx.run_until_parked();
25115    cx.assert_editor_state(indoc! {"
25116        if [ \"$1\" = \"test\" ]; then
25117        else
25118            ˇ
25119    "});
25120
25121    // test correct indent after newline after `elif`
25122    cx.set_state(indoc! {"
25123        if [ \"$1\" = \"test\" ]; then
25124        elifˇ
25125    "});
25126    cx.update_editor(|editor, window, cx| {
25127        editor.newline(&Newline, window, cx);
25128    });
25129    cx.run_until_parked();
25130    cx.assert_editor_state(indoc! {"
25131        if [ \"$1\" = \"test\" ]; then
25132        elif
25133            ˇ
25134    "});
25135
25136    // test correct indent after newline after `do`
25137    cx.set_state(indoc! {"
25138        for file in *.txt; doˇ
25139    "});
25140    cx.update_editor(|editor, window, cx| {
25141        editor.newline(&Newline, window, cx);
25142    });
25143    cx.run_until_parked();
25144    cx.assert_editor_state(indoc! {"
25145        for file in *.txt; do
25146            ˇ
25147    "});
25148
25149    // test correct indent after newline after case pattern
25150    cx.set_state(indoc! {"
25151        case \"$1\" in
25152            start)ˇ
25153    "});
25154    cx.update_editor(|editor, window, cx| {
25155        editor.newline(&Newline, window, cx);
25156    });
25157    cx.run_until_parked();
25158    cx.assert_editor_state(indoc! {"
25159        case \"$1\" in
25160            start)
25161                ˇ
25162    "});
25163
25164    // test correct indent after newline after case pattern
25165    cx.set_state(indoc! {"
25166        case \"$1\" in
25167            start)
25168                ;;
25169            *)ˇ
25170    "});
25171    cx.update_editor(|editor, window, cx| {
25172        editor.newline(&Newline, window, cx);
25173    });
25174    cx.run_until_parked();
25175    cx.assert_editor_state(indoc! {"
25176        case \"$1\" in
25177            start)
25178                ;;
25179            *)
25180                ˇ
25181    "});
25182
25183    // test correct indent after newline after function opening brace
25184    cx.set_state(indoc! {"
25185        function test() {ˇ}
25186    "});
25187    cx.update_editor(|editor, window, cx| {
25188        editor.newline(&Newline, window, cx);
25189    });
25190    cx.run_until_parked();
25191    cx.assert_editor_state(indoc! {"
25192        function test() {
25193            ˇ
25194        }
25195    "});
25196
25197    // test no extra indent after semicolon on same line
25198    cx.set_state(indoc! {"
25199        echo \"test\"25200    "});
25201    cx.update_editor(|editor, window, cx| {
25202        editor.newline(&Newline, window, cx);
25203    });
25204    cx.run_until_parked();
25205    cx.assert_editor_state(indoc! {"
25206        echo \"test\";
25207        ˇ
25208    "});
25209}
25210
25211fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25212    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25213    point..point
25214}
25215
25216#[track_caller]
25217fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25218    let (text, ranges) = marked_text_ranges(marked_text, true);
25219    assert_eq!(editor.text(cx), text);
25220    assert_eq!(
25221        editor.selections.ranges(cx),
25222        ranges,
25223        "Assert selections are {}",
25224        marked_text
25225    );
25226}
25227
25228pub fn handle_signature_help_request(
25229    cx: &mut EditorLspTestContext,
25230    mocked_response: lsp::SignatureHelp,
25231) -> impl Future<Output = ()> + use<> {
25232    let mut request =
25233        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25234            let mocked_response = mocked_response.clone();
25235            async move { Ok(Some(mocked_response)) }
25236        });
25237
25238    async move {
25239        request.next().await;
25240    }
25241}
25242
25243#[track_caller]
25244pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25245    cx.update_editor(|editor, _, _| {
25246        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25247            let entries = menu.entries.borrow();
25248            let entries = entries
25249                .iter()
25250                .map(|entry| entry.string.as_str())
25251                .collect::<Vec<_>>();
25252            assert_eq!(entries, expected);
25253        } else {
25254            panic!("Expected completions menu");
25255        }
25256    });
25257}
25258
25259/// Handle completion request passing a marked string specifying where the completion
25260/// should be triggered from using '|' character, what range should be replaced, and what completions
25261/// should be returned using '<' and '>' to delimit the range.
25262///
25263/// Also see `handle_completion_request_with_insert_and_replace`.
25264#[track_caller]
25265pub fn handle_completion_request(
25266    marked_string: &str,
25267    completions: Vec<&'static str>,
25268    is_incomplete: bool,
25269    counter: Arc<AtomicUsize>,
25270    cx: &mut EditorLspTestContext,
25271) -> impl Future<Output = ()> {
25272    let complete_from_marker: TextRangeMarker = '|'.into();
25273    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25274    let (_, mut marked_ranges) = marked_text_ranges_by(
25275        marked_string,
25276        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25277    );
25278
25279    let complete_from_position =
25280        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25281    let replace_range =
25282        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25283
25284    let mut request =
25285        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25286            let completions = completions.clone();
25287            counter.fetch_add(1, atomic::Ordering::Release);
25288            async move {
25289                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25290                assert_eq!(
25291                    params.text_document_position.position,
25292                    complete_from_position
25293                );
25294                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25295                    is_incomplete,
25296                    item_defaults: None,
25297                    items: completions
25298                        .iter()
25299                        .map(|completion_text| lsp::CompletionItem {
25300                            label: completion_text.to_string(),
25301                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25302                                range: replace_range,
25303                                new_text: completion_text.to_string(),
25304                            })),
25305                            ..Default::default()
25306                        })
25307                        .collect(),
25308                })))
25309            }
25310        });
25311
25312    async move {
25313        request.next().await;
25314    }
25315}
25316
25317/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25318/// given instead, which also contains an `insert` range.
25319///
25320/// This function uses markers to define ranges:
25321/// - `|` marks the cursor position
25322/// - `<>` marks the replace range
25323/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25324pub fn handle_completion_request_with_insert_and_replace(
25325    cx: &mut EditorLspTestContext,
25326    marked_string: &str,
25327    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25328    counter: Arc<AtomicUsize>,
25329) -> impl Future<Output = ()> {
25330    let complete_from_marker: TextRangeMarker = '|'.into();
25331    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25332    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25333
25334    let (_, mut marked_ranges) = marked_text_ranges_by(
25335        marked_string,
25336        vec![
25337            complete_from_marker.clone(),
25338            replace_range_marker.clone(),
25339            insert_range_marker.clone(),
25340        ],
25341    );
25342
25343    let complete_from_position =
25344        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25345    let replace_range =
25346        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25347
25348    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25349        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25350        _ => lsp::Range {
25351            start: replace_range.start,
25352            end: complete_from_position,
25353        },
25354    };
25355
25356    let mut request =
25357        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25358            let completions = completions.clone();
25359            counter.fetch_add(1, atomic::Ordering::Release);
25360            async move {
25361                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25362                assert_eq!(
25363                    params.text_document_position.position, complete_from_position,
25364                    "marker `|` position doesn't match",
25365                );
25366                Ok(Some(lsp::CompletionResponse::Array(
25367                    completions
25368                        .iter()
25369                        .map(|(label, new_text)| lsp::CompletionItem {
25370                            label: label.to_string(),
25371                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25372                                lsp::InsertReplaceEdit {
25373                                    insert: insert_range,
25374                                    replace: replace_range,
25375                                    new_text: new_text.to_string(),
25376                                },
25377                            )),
25378                            ..Default::default()
25379                        })
25380                        .collect(),
25381                )))
25382            }
25383        });
25384
25385    async move {
25386        request.next().await;
25387    }
25388}
25389
25390fn handle_resolve_completion_request(
25391    cx: &mut EditorLspTestContext,
25392    edits: Option<Vec<(&'static str, &'static str)>>,
25393) -> impl Future<Output = ()> {
25394    let edits = edits.map(|edits| {
25395        edits
25396            .iter()
25397            .map(|(marked_string, new_text)| {
25398                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25399                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25400                lsp::TextEdit::new(replace_range, new_text.to_string())
25401            })
25402            .collect::<Vec<_>>()
25403    });
25404
25405    let mut request =
25406        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25407            let edits = edits.clone();
25408            async move {
25409                Ok(lsp::CompletionItem {
25410                    additional_text_edits: edits,
25411                    ..Default::default()
25412                })
25413            }
25414        });
25415
25416    async move {
25417        request.next().await;
25418    }
25419}
25420
25421pub(crate) fn update_test_language_settings(
25422    cx: &mut TestAppContext,
25423    f: impl Fn(&mut AllLanguageSettingsContent),
25424) {
25425    cx.update(|cx| {
25426        SettingsStore::update_global(cx, |store, cx| {
25427            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25428        });
25429    });
25430}
25431
25432pub(crate) fn update_test_project_settings(
25433    cx: &mut TestAppContext,
25434    f: impl Fn(&mut ProjectSettingsContent),
25435) {
25436    cx.update(|cx| {
25437        SettingsStore::update_global(cx, |store, cx| {
25438            store.update_user_settings(cx, |settings| f(&mut settings.project));
25439        });
25440    });
25441}
25442
25443pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25444    cx.update(|cx| {
25445        assets::Assets.load_test_fonts(cx);
25446        let store = SettingsStore::test(cx);
25447        cx.set_global(store);
25448        theme::init(theme::LoadThemes::JustBase, cx);
25449        release_channel::init(SemanticVersion::default(), cx);
25450        client::init_settings(cx);
25451        language::init(cx);
25452        Project::init_settings(cx);
25453        workspace::init_settings(cx);
25454        crate::init(cx);
25455    });
25456    zlog::init_test();
25457    update_test_language_settings(cx, f);
25458}
25459
25460#[track_caller]
25461fn assert_hunk_revert(
25462    not_reverted_text_with_selections: &str,
25463    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25464    expected_reverted_text_with_selections: &str,
25465    base_text: &str,
25466    cx: &mut EditorLspTestContext,
25467) {
25468    cx.set_state(not_reverted_text_with_selections);
25469    cx.set_head_text(base_text);
25470    cx.executor().run_until_parked();
25471
25472    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25473        let snapshot = editor.snapshot(window, cx);
25474        let reverted_hunk_statuses = snapshot
25475            .buffer_snapshot()
25476            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25477            .map(|hunk| hunk.status().kind)
25478            .collect::<Vec<_>>();
25479
25480        editor.git_restore(&Default::default(), window, cx);
25481        reverted_hunk_statuses
25482    });
25483    cx.executor().run_until_parked();
25484    cx.assert_editor_state(expected_reverted_text_with_selections);
25485    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25486}
25487
25488#[gpui::test(iterations = 10)]
25489async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25490    init_test(cx, |_| {});
25491
25492    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25493    let counter = diagnostic_requests.clone();
25494
25495    let fs = FakeFs::new(cx.executor());
25496    fs.insert_tree(
25497        path!("/a"),
25498        json!({
25499            "first.rs": "fn main() { let a = 5; }",
25500            "second.rs": "// Test file",
25501        }),
25502    )
25503    .await;
25504
25505    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25506    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25507    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25508
25509    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25510    language_registry.add(rust_lang());
25511    let mut fake_servers = language_registry.register_fake_lsp(
25512        "Rust",
25513        FakeLspAdapter {
25514            capabilities: lsp::ServerCapabilities {
25515                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25516                    lsp::DiagnosticOptions {
25517                        identifier: None,
25518                        inter_file_dependencies: true,
25519                        workspace_diagnostics: true,
25520                        work_done_progress_options: Default::default(),
25521                    },
25522                )),
25523                ..Default::default()
25524            },
25525            ..Default::default()
25526        },
25527    );
25528
25529    let editor = workspace
25530        .update(cx, |workspace, window, cx| {
25531            workspace.open_abs_path(
25532                PathBuf::from(path!("/a/first.rs")),
25533                OpenOptions::default(),
25534                window,
25535                cx,
25536            )
25537        })
25538        .unwrap()
25539        .await
25540        .unwrap()
25541        .downcast::<Editor>()
25542        .unwrap();
25543    let fake_server = fake_servers.next().await.unwrap();
25544    let server_id = fake_server.server.server_id();
25545    let mut first_request = fake_server
25546        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25547            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25548            let result_id = Some(new_result_id.to_string());
25549            assert_eq!(
25550                params.text_document.uri,
25551                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25552            );
25553            async move {
25554                Ok(lsp::DocumentDiagnosticReportResult::Report(
25555                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25556                        related_documents: None,
25557                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25558                            items: Vec::new(),
25559                            result_id,
25560                        },
25561                    }),
25562                ))
25563            }
25564        });
25565
25566    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25567        project.update(cx, |project, cx| {
25568            let buffer_id = editor
25569                .read(cx)
25570                .buffer()
25571                .read(cx)
25572                .as_singleton()
25573                .expect("created a singleton buffer")
25574                .read(cx)
25575                .remote_id();
25576            let buffer_result_id = project
25577                .lsp_store()
25578                .read(cx)
25579                .result_id(server_id, buffer_id, cx);
25580            assert_eq!(expected, buffer_result_id);
25581        });
25582    };
25583
25584    ensure_result_id(None, cx);
25585    cx.executor().advance_clock(Duration::from_millis(60));
25586    cx.executor().run_until_parked();
25587    assert_eq!(
25588        diagnostic_requests.load(atomic::Ordering::Acquire),
25589        1,
25590        "Opening file should trigger diagnostic request"
25591    );
25592    first_request
25593        .next()
25594        .await
25595        .expect("should have sent the first diagnostics pull request");
25596    ensure_result_id(Some("1".to_string()), cx);
25597
25598    // Editing should trigger diagnostics
25599    editor.update_in(cx, |editor, window, cx| {
25600        editor.handle_input("2", window, cx)
25601    });
25602    cx.executor().advance_clock(Duration::from_millis(60));
25603    cx.executor().run_until_parked();
25604    assert_eq!(
25605        diagnostic_requests.load(atomic::Ordering::Acquire),
25606        2,
25607        "Editing should trigger diagnostic request"
25608    );
25609    ensure_result_id(Some("2".to_string()), cx);
25610
25611    // Moving cursor should not trigger diagnostic request
25612    editor.update_in(cx, |editor, window, cx| {
25613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25614            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25615        });
25616    });
25617    cx.executor().advance_clock(Duration::from_millis(60));
25618    cx.executor().run_until_parked();
25619    assert_eq!(
25620        diagnostic_requests.load(atomic::Ordering::Acquire),
25621        2,
25622        "Cursor movement should not trigger diagnostic request"
25623    );
25624    ensure_result_id(Some("2".to_string()), cx);
25625    // Multiple rapid edits should be debounced
25626    for _ in 0..5 {
25627        editor.update_in(cx, |editor, window, cx| {
25628            editor.handle_input("x", window, cx)
25629        });
25630    }
25631    cx.executor().advance_clock(Duration::from_millis(60));
25632    cx.executor().run_until_parked();
25633
25634    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25635    assert!(
25636        final_requests <= 4,
25637        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25638    );
25639    ensure_result_id(Some(final_requests.to_string()), cx);
25640}
25641
25642#[gpui::test]
25643async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25644    // Regression test for issue #11671
25645    // Previously, adding a cursor after moving multiple cursors would reset
25646    // the cursor count instead of adding to the existing cursors.
25647    init_test(cx, |_| {});
25648    let mut cx = EditorTestContext::new(cx).await;
25649
25650    // Create a simple buffer with cursor at start
25651    cx.set_state(indoc! {"
25652        ˇaaaa
25653        bbbb
25654        cccc
25655        dddd
25656        eeee
25657        ffff
25658        gggg
25659        hhhh"});
25660
25661    // Add 2 cursors below (so we have 3 total)
25662    cx.update_editor(|editor, window, cx| {
25663        editor.add_selection_below(&Default::default(), window, cx);
25664        editor.add_selection_below(&Default::default(), window, cx);
25665    });
25666
25667    // Verify we have 3 cursors
25668    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25669    assert_eq!(
25670        initial_count, 3,
25671        "Should have 3 cursors after adding 2 below"
25672    );
25673
25674    // Move down one line
25675    cx.update_editor(|editor, window, cx| {
25676        editor.move_down(&MoveDown, window, cx);
25677    });
25678
25679    // Add another cursor below
25680    cx.update_editor(|editor, window, cx| {
25681        editor.add_selection_below(&Default::default(), window, cx);
25682    });
25683
25684    // Should now have 4 cursors (3 original + 1 new)
25685    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25686    assert_eq!(
25687        final_count, 4,
25688        "Should have 4 cursors after moving and adding another"
25689    );
25690}
25691
25692#[gpui::test(iterations = 10)]
25693async fn test_document_colors(cx: &mut TestAppContext) {
25694    let expected_color = Rgba {
25695        r: 0.33,
25696        g: 0.33,
25697        b: 0.33,
25698        a: 0.33,
25699    };
25700
25701    init_test(cx, |_| {});
25702
25703    let fs = FakeFs::new(cx.executor());
25704    fs.insert_tree(
25705        path!("/a"),
25706        json!({
25707            "first.rs": "fn main() { let a = 5; }",
25708        }),
25709    )
25710    .await;
25711
25712    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25713    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25714    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25715
25716    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25717    language_registry.add(rust_lang());
25718    let mut fake_servers = language_registry.register_fake_lsp(
25719        "Rust",
25720        FakeLspAdapter {
25721            capabilities: lsp::ServerCapabilities {
25722                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25723                ..lsp::ServerCapabilities::default()
25724            },
25725            name: "rust-analyzer",
25726            ..FakeLspAdapter::default()
25727        },
25728    );
25729    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25730        "Rust",
25731        FakeLspAdapter {
25732            capabilities: lsp::ServerCapabilities {
25733                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25734                ..lsp::ServerCapabilities::default()
25735            },
25736            name: "not-rust-analyzer",
25737            ..FakeLspAdapter::default()
25738        },
25739    );
25740
25741    let editor = workspace
25742        .update(cx, |workspace, window, cx| {
25743            workspace.open_abs_path(
25744                PathBuf::from(path!("/a/first.rs")),
25745                OpenOptions::default(),
25746                window,
25747                cx,
25748            )
25749        })
25750        .unwrap()
25751        .await
25752        .unwrap()
25753        .downcast::<Editor>()
25754        .unwrap();
25755    let fake_language_server = fake_servers.next().await.unwrap();
25756    let fake_language_server_without_capabilities =
25757        fake_servers_without_capabilities.next().await.unwrap();
25758    let requests_made = Arc::new(AtomicUsize::new(0));
25759    let closure_requests_made = Arc::clone(&requests_made);
25760    let mut color_request_handle = fake_language_server
25761        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25762            let requests_made = Arc::clone(&closure_requests_made);
25763            async move {
25764                assert_eq!(
25765                    params.text_document.uri,
25766                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25767                );
25768                requests_made.fetch_add(1, atomic::Ordering::Release);
25769                Ok(vec![
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                    lsp::ColorInformation {
25789                        range: lsp::Range {
25790                            start: lsp::Position {
25791                                line: 0,
25792                                character: 0,
25793                            },
25794                            end: lsp::Position {
25795                                line: 0,
25796                                character: 1,
25797                            },
25798                        },
25799                        color: lsp::Color {
25800                            red: 0.33,
25801                            green: 0.33,
25802                            blue: 0.33,
25803                            alpha: 0.33,
25804                        },
25805                    },
25806                ])
25807            }
25808        });
25809
25810    let _handle = fake_language_server_without_capabilities
25811        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25812            panic!("Should not be called");
25813        });
25814    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25815    color_request_handle.next().await.unwrap();
25816    cx.run_until_parked();
25817    assert_eq!(
25818        1,
25819        requests_made.load(atomic::Ordering::Acquire),
25820        "Should query for colors once per editor open"
25821    );
25822    editor.update_in(cx, |editor, _, cx| {
25823        assert_eq!(
25824            vec![expected_color],
25825            extract_color_inlays(editor, cx),
25826            "Should have an initial inlay"
25827        );
25828    });
25829
25830    // opening another file in a split should not influence the LSP query counter
25831    workspace
25832        .update(cx, |workspace, window, cx| {
25833            assert_eq!(
25834                workspace.panes().len(),
25835                1,
25836                "Should have one pane with one editor"
25837            );
25838            workspace.move_item_to_pane_in_direction(
25839                &MoveItemToPaneInDirection {
25840                    direction: SplitDirection::Right,
25841                    focus: false,
25842                    clone: true,
25843                },
25844                window,
25845                cx,
25846            );
25847        })
25848        .unwrap();
25849    cx.run_until_parked();
25850    workspace
25851        .update(cx, |workspace, _, cx| {
25852            let panes = workspace.panes();
25853            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25854            for pane in panes {
25855                let editor = pane
25856                    .read(cx)
25857                    .active_item()
25858                    .and_then(|item| item.downcast::<Editor>())
25859                    .expect("Should have opened an editor in each split");
25860                let editor_file = editor
25861                    .read(cx)
25862                    .buffer()
25863                    .read(cx)
25864                    .as_singleton()
25865                    .expect("test deals with singleton buffers")
25866                    .read(cx)
25867                    .file()
25868                    .expect("test buffese should have a file")
25869                    .path();
25870                assert_eq!(
25871                    editor_file.as_ref(),
25872                    rel_path("first.rs"),
25873                    "Both editors should be opened for the same file"
25874                )
25875            }
25876        })
25877        .unwrap();
25878
25879    cx.executor().advance_clock(Duration::from_millis(500));
25880    let save = editor.update_in(cx, |editor, window, cx| {
25881        editor.move_to_end(&MoveToEnd, window, cx);
25882        editor.handle_input("dirty", window, cx);
25883        editor.save(
25884            SaveOptions {
25885                format: true,
25886                autosave: true,
25887            },
25888            project.clone(),
25889            window,
25890            cx,
25891        )
25892    });
25893    save.await.unwrap();
25894
25895    color_request_handle.next().await.unwrap();
25896    cx.run_until_parked();
25897    assert_eq!(
25898        2,
25899        requests_made.load(atomic::Ordering::Acquire),
25900        "Should query for colors once per save (deduplicated) and once per formatting after save"
25901    );
25902
25903    drop(editor);
25904    let close = workspace
25905        .update(cx, |workspace, window, cx| {
25906            workspace.active_pane().update(cx, |pane, cx| {
25907                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25908            })
25909        })
25910        .unwrap();
25911    close.await.unwrap();
25912    let close = workspace
25913        .update(cx, |workspace, window, cx| {
25914            workspace.active_pane().update(cx, |pane, cx| {
25915                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25916            })
25917        })
25918        .unwrap();
25919    close.await.unwrap();
25920    assert_eq!(
25921        2,
25922        requests_made.load(atomic::Ordering::Acquire),
25923        "After saving and closing all editors, no extra requests should be made"
25924    );
25925    workspace
25926        .update(cx, |workspace, _, cx| {
25927            assert!(
25928                workspace.active_item(cx).is_none(),
25929                "Should close all editors"
25930            )
25931        })
25932        .unwrap();
25933
25934    workspace
25935        .update(cx, |workspace, window, cx| {
25936            workspace.active_pane().update(cx, |pane, cx| {
25937                pane.navigate_backward(&workspace::GoBack, window, cx);
25938            })
25939        })
25940        .unwrap();
25941    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25942    cx.run_until_parked();
25943    let editor = workspace
25944        .update(cx, |workspace, _, cx| {
25945            workspace
25946                .active_item(cx)
25947                .expect("Should have reopened the editor again after navigating back")
25948                .downcast::<Editor>()
25949                .expect("Should be an editor")
25950        })
25951        .unwrap();
25952
25953    assert_eq!(
25954        2,
25955        requests_made.load(atomic::Ordering::Acquire),
25956        "Cache should be reused on buffer close and reopen"
25957    );
25958    editor.update(cx, |editor, cx| {
25959        assert_eq!(
25960            vec![expected_color],
25961            extract_color_inlays(editor, cx),
25962            "Should have an initial inlay"
25963        );
25964    });
25965
25966    drop(color_request_handle);
25967    let closure_requests_made = Arc::clone(&requests_made);
25968    let mut empty_color_request_handle = fake_language_server
25969        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25970            let requests_made = Arc::clone(&closure_requests_made);
25971            async move {
25972                assert_eq!(
25973                    params.text_document.uri,
25974                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25975                );
25976                requests_made.fetch_add(1, atomic::Ordering::Release);
25977                Ok(Vec::new())
25978            }
25979        });
25980    let save = editor.update_in(cx, |editor, window, cx| {
25981        editor.move_to_end(&MoveToEnd, window, cx);
25982        editor.handle_input("dirty_again", window, cx);
25983        editor.save(
25984            SaveOptions {
25985                format: false,
25986                autosave: true,
25987            },
25988            project.clone(),
25989            window,
25990            cx,
25991        )
25992    });
25993    save.await.unwrap();
25994
25995    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
25996    empty_color_request_handle.next().await.unwrap();
25997    cx.run_until_parked();
25998    assert_eq!(
25999        3,
26000        requests_made.load(atomic::Ordering::Acquire),
26001        "Should query for colors once per save only, as formatting was not requested"
26002    );
26003    editor.update(cx, |editor, cx| {
26004        assert_eq!(
26005            Vec::<Rgba>::new(),
26006            extract_color_inlays(editor, cx),
26007            "Should clear all colors when the server returns an empty response"
26008        );
26009    });
26010}
26011
26012#[gpui::test]
26013async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26014    init_test(cx, |_| {});
26015    let (editor, cx) = cx.add_window_view(Editor::single_line);
26016    editor.update_in(cx, |editor, window, cx| {
26017        editor.set_text("oops\n\nwow\n", window, cx)
26018    });
26019    cx.run_until_parked();
26020    editor.update(cx, |editor, cx| {
26021        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26022    });
26023    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26024    cx.run_until_parked();
26025    editor.update(cx, |editor, cx| {
26026        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26027    });
26028}
26029
26030#[gpui::test]
26031async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26032    init_test(cx, |_| {});
26033
26034    cx.update(|cx| {
26035        register_project_item::<Editor>(cx);
26036    });
26037
26038    let fs = FakeFs::new(cx.executor());
26039    fs.insert_tree("/root1", json!({})).await;
26040    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26041        .await;
26042
26043    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26044    let (workspace, cx) =
26045        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26046
26047    let worktree_id = project.update(cx, |project, cx| {
26048        project.worktrees(cx).next().unwrap().read(cx).id()
26049    });
26050
26051    let handle = workspace
26052        .update_in(cx, |workspace, window, cx| {
26053            let project_path = (worktree_id, rel_path("one.pdf"));
26054            workspace.open_path(project_path, None, true, window, cx)
26055        })
26056        .await
26057        .unwrap();
26058
26059    assert_eq!(
26060        handle.to_any().entity_type(),
26061        TypeId::of::<InvalidBufferView>()
26062    );
26063}
26064
26065#[gpui::test]
26066async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26067    init_test(cx, |_| {});
26068
26069    let language = Arc::new(Language::new(
26070        LanguageConfig::default(),
26071        Some(tree_sitter_rust::LANGUAGE.into()),
26072    ));
26073
26074    // Test hierarchical sibling navigation
26075    let text = r#"
26076        fn outer() {
26077            if condition {
26078                let a = 1;
26079            }
26080            let b = 2;
26081        }
26082
26083        fn another() {
26084            let c = 3;
26085        }
26086    "#;
26087
26088    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26089    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26090    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26091
26092    // Wait for parsing to complete
26093    editor
26094        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26095        .await;
26096
26097    editor.update_in(cx, |editor, window, cx| {
26098        // Start by selecting "let a = 1;" inside the if block
26099        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26100            s.select_display_ranges([
26101                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26102            ]);
26103        });
26104
26105        let initial_selection = editor.selections.display_ranges(cx);
26106        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26107
26108        // Test select next sibling - should move up levels to find the next sibling
26109        // Since "let a = 1;" has no siblings in the if block, it should move up
26110        // to find "let b = 2;" which is a sibling of the if block
26111        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26112        let next_selection = editor.selections.display_ranges(cx);
26113
26114        // Should have a selection and it should be different from the initial
26115        assert_eq!(
26116            next_selection.len(),
26117            1,
26118            "Should have one selection after next"
26119        );
26120        assert_ne!(
26121            next_selection[0], initial_selection[0],
26122            "Next sibling selection should be different"
26123        );
26124
26125        // Test hierarchical navigation by going to the end of the current function
26126        // and trying to navigate to the next function
26127        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26128            s.select_display_ranges([
26129                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26130            ]);
26131        });
26132
26133        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26134        let function_next_selection = editor.selections.display_ranges(cx);
26135
26136        // Should move to the next function
26137        assert_eq!(
26138            function_next_selection.len(),
26139            1,
26140            "Should have one selection after function next"
26141        );
26142
26143        // Test select previous sibling navigation
26144        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26145        let prev_selection = editor.selections.display_ranges(cx);
26146
26147        // Should have a selection and it should be different
26148        assert_eq!(
26149            prev_selection.len(),
26150            1,
26151            "Should have one selection after prev"
26152        );
26153        assert_ne!(
26154            prev_selection[0], function_next_selection[0],
26155            "Previous sibling selection should be different from next"
26156        );
26157    });
26158}
26159
26160#[gpui::test]
26161async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26162    init_test(cx, |_| {});
26163
26164    let mut cx = EditorTestContext::new(cx).await;
26165    cx.set_state(
26166        "let ˇvariable = 42;
26167let another = variable + 1;
26168let result = variable * 2;",
26169    );
26170
26171    // Set up document highlights manually (simulating LSP response)
26172    cx.update_editor(|editor, _window, cx| {
26173        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26174
26175        // Create highlights for "variable" occurrences
26176        let highlight_ranges = [
26177            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26178            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26179            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26180        ];
26181
26182        let anchor_ranges: Vec<_> = highlight_ranges
26183            .iter()
26184            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26185            .collect();
26186
26187        editor.highlight_background::<DocumentHighlightRead>(
26188            &anchor_ranges,
26189            |theme| theme.colors().editor_document_highlight_read_background,
26190            cx,
26191        );
26192    });
26193
26194    // Go to next highlight - should move to second "variable"
26195    cx.update_editor(|editor, window, cx| {
26196        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26197    });
26198    cx.assert_editor_state(
26199        "let variable = 42;
26200let another = ˇvariable + 1;
26201let result = variable * 2;",
26202    );
26203
26204    // Go to next highlight - should move to third "variable"
26205    cx.update_editor(|editor, window, cx| {
26206        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26207    });
26208    cx.assert_editor_state(
26209        "let variable = 42;
26210let another = variable + 1;
26211let result = ˇvariable * 2;",
26212    );
26213
26214    // Go to next highlight - should stay at third "variable" (no wrap-around)
26215    cx.update_editor(|editor, window, cx| {
26216        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26217    });
26218    cx.assert_editor_state(
26219        "let variable = 42;
26220let another = variable + 1;
26221let result = ˇvariable * 2;",
26222    );
26223
26224    // Now test going backwards from third position
26225    cx.update_editor(|editor, window, cx| {
26226        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26227    });
26228    cx.assert_editor_state(
26229        "let variable = 42;
26230let another = ˇvariable + 1;
26231let result = variable * 2;",
26232    );
26233
26234    // Go to previous highlight - should move to first "variable"
26235    cx.update_editor(|editor, window, cx| {
26236        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26237    });
26238    cx.assert_editor_state(
26239        "let ˇvariable = 42;
26240let another = variable + 1;
26241let result = variable * 2;",
26242    );
26243
26244    // Go to previous highlight - should stay on first "variable"
26245    cx.update_editor(|editor, window, cx| {
26246        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26247    });
26248    cx.assert_editor_state(
26249        "let ˇvariable = 42;
26250let another = variable + 1;
26251let result = variable * 2;",
26252    );
26253}
26254
26255#[gpui::test]
26256async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26257    cx: &mut gpui::TestAppContext,
26258) {
26259    init_test(cx, |_| {});
26260
26261    let url = "https://zed.dev";
26262
26263    let markdown_language = Arc::new(Language::new(
26264        LanguageConfig {
26265            name: "Markdown".into(),
26266            ..LanguageConfig::default()
26267        },
26268        None,
26269    ));
26270
26271    let mut cx = EditorTestContext::new(cx).await;
26272    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26273    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26274
26275    cx.update_editor(|editor, window, cx| {
26276        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26277        editor.paste(&Paste, window, cx);
26278    });
26279
26280    cx.assert_editor_state(&format!(
26281        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26282    ));
26283}
26284
26285#[gpui::test]
26286async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26287    cx: &mut gpui::TestAppContext,
26288) {
26289    init_test(cx, |_| {});
26290
26291    let url = "https://zed.dev";
26292
26293    let markdown_language = Arc::new(Language::new(
26294        LanguageConfig {
26295            name: "Markdown".into(),
26296            ..LanguageConfig::default()
26297        },
26298        None,
26299    ));
26300
26301    let mut cx = EditorTestContext::new(cx).await;
26302    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26303    cx.set_state(&format!(
26304        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26305    ));
26306
26307    cx.update_editor(|editor, window, cx| {
26308        editor.copy(&Copy, window, cx);
26309    });
26310
26311    cx.set_state(&format!(
26312        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26313    ));
26314
26315    cx.update_editor(|editor, window, cx| {
26316        editor.paste(&Paste, window, cx);
26317    });
26318
26319    cx.assert_editor_state(&format!(
26320        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26321    ));
26322}
26323
26324#[gpui::test]
26325async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26326    cx: &mut gpui::TestAppContext,
26327) {
26328    init_test(cx, |_| {});
26329
26330    let url = "https://zed.dev";
26331
26332    let markdown_language = Arc::new(Language::new(
26333        LanguageConfig {
26334            name: "Markdown".into(),
26335            ..LanguageConfig::default()
26336        },
26337        None,
26338    ));
26339
26340    let mut cx = EditorTestContext::new(cx).await;
26341    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26342    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26343
26344    cx.update_editor(|editor, window, cx| {
26345        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26346        editor.paste(&Paste, window, cx);
26347    });
26348
26349    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26350}
26351
26352#[gpui::test]
26353async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26354    cx: &mut gpui::TestAppContext,
26355) {
26356    init_test(cx, |_| {});
26357
26358    let text = "Awesome";
26359
26360    let markdown_language = Arc::new(Language::new(
26361        LanguageConfig {
26362            name: "Markdown".into(),
26363            ..LanguageConfig::default()
26364        },
26365        None,
26366    ));
26367
26368    let mut cx = EditorTestContext::new(cx).await;
26369    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26370    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26371
26372    cx.update_editor(|editor, window, cx| {
26373        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26374        editor.paste(&Paste, window, cx);
26375    });
26376
26377    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26378}
26379
26380#[gpui::test]
26381async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26382    cx: &mut gpui::TestAppContext,
26383) {
26384    init_test(cx, |_| {});
26385
26386    let url = "https://zed.dev";
26387
26388    let markdown_language = Arc::new(Language::new(
26389        LanguageConfig {
26390            name: "Rust".into(),
26391            ..LanguageConfig::default()
26392        },
26393        None,
26394    ));
26395
26396    let mut cx = EditorTestContext::new(cx).await;
26397    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26398    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26399
26400    cx.update_editor(|editor, window, cx| {
26401        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26402        editor.paste(&Paste, window, cx);
26403    });
26404
26405    cx.assert_editor_state(&format!(
26406        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26407    ));
26408}
26409
26410#[gpui::test]
26411async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26412    cx: &mut TestAppContext,
26413) {
26414    init_test(cx, |_| {});
26415
26416    let url = "https://zed.dev";
26417
26418    let markdown_language = Arc::new(Language::new(
26419        LanguageConfig {
26420            name: "Markdown".into(),
26421            ..LanguageConfig::default()
26422        },
26423        None,
26424    ));
26425
26426    let (editor, cx) = cx.add_window_view(|window, cx| {
26427        let multi_buffer = MultiBuffer::build_multi(
26428            [
26429                ("this will embed -> link", vec![Point::row_range(0..1)]),
26430                ("this will replace -> link", vec![Point::row_range(0..1)]),
26431            ],
26432            cx,
26433        );
26434        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26435        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26436            s.select_ranges(vec![
26437                Point::new(0, 19)..Point::new(0, 23),
26438                Point::new(1, 21)..Point::new(1, 25),
26439            ])
26440        });
26441        let first_buffer_id = multi_buffer
26442            .read(cx)
26443            .excerpt_buffer_ids()
26444            .into_iter()
26445            .next()
26446            .unwrap();
26447        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26448        first_buffer.update(cx, |buffer, cx| {
26449            buffer.set_language(Some(markdown_language.clone()), cx);
26450        });
26451
26452        editor
26453    });
26454    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26455
26456    cx.update_editor(|editor, window, cx| {
26457        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26458        editor.paste(&Paste, window, cx);
26459    });
26460
26461    cx.assert_editor_state(&format!(
26462        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26463    ));
26464}
26465
26466#[gpui::test]
26467async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26468    init_test(cx, |_| {});
26469
26470    let fs = FakeFs::new(cx.executor());
26471    fs.insert_tree(
26472        path!("/project"),
26473        json!({
26474            "first.rs": "# First Document\nSome content here.",
26475            "second.rs": "Plain text content for second file.",
26476        }),
26477    )
26478    .await;
26479
26480    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26481    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26482    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26483
26484    let language = rust_lang();
26485    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26486    language_registry.add(language.clone());
26487    let mut fake_servers = language_registry.register_fake_lsp(
26488        "Rust",
26489        FakeLspAdapter {
26490            ..FakeLspAdapter::default()
26491        },
26492    );
26493
26494    let buffer1 = project
26495        .update(cx, |project, cx| {
26496            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26497        })
26498        .await
26499        .unwrap();
26500    let buffer2 = project
26501        .update(cx, |project, cx| {
26502            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26503        })
26504        .await
26505        .unwrap();
26506
26507    let multi_buffer = cx.new(|cx| {
26508        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26509        multi_buffer.set_excerpts_for_path(
26510            PathKey::for_buffer(&buffer1, cx),
26511            buffer1.clone(),
26512            [Point::zero()..buffer1.read(cx).max_point()],
26513            3,
26514            cx,
26515        );
26516        multi_buffer.set_excerpts_for_path(
26517            PathKey::for_buffer(&buffer2, cx),
26518            buffer2.clone(),
26519            [Point::zero()..buffer1.read(cx).max_point()],
26520            3,
26521            cx,
26522        );
26523        multi_buffer
26524    });
26525
26526    let (editor, cx) = cx.add_window_view(|window, cx| {
26527        Editor::new(
26528            EditorMode::full(),
26529            multi_buffer,
26530            Some(project.clone()),
26531            window,
26532            cx,
26533        )
26534    });
26535
26536    let fake_language_server = fake_servers.next().await.unwrap();
26537
26538    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26539
26540    let save = editor.update_in(cx, |editor, window, cx| {
26541        assert!(editor.is_dirty(cx));
26542
26543        editor.save(
26544            SaveOptions {
26545                format: true,
26546                autosave: true,
26547            },
26548            project,
26549            window,
26550            cx,
26551        )
26552    });
26553    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26554    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26555    let mut done_edit_rx = Some(done_edit_rx);
26556    let mut start_edit_tx = Some(start_edit_tx);
26557
26558    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26559        start_edit_tx.take().unwrap().send(()).unwrap();
26560        let done_edit_rx = done_edit_rx.take().unwrap();
26561        async move {
26562            done_edit_rx.await.unwrap();
26563            Ok(None)
26564        }
26565    });
26566
26567    start_edit_rx.await.unwrap();
26568    buffer2
26569        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26570        .unwrap();
26571
26572    done_edit_tx.send(()).unwrap();
26573
26574    save.await.unwrap();
26575    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26576}
26577
26578#[track_caller]
26579fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26580    editor
26581        .all_inlays(cx)
26582        .into_iter()
26583        .filter_map(|inlay| inlay.get_color())
26584        .map(Rgba::from)
26585        .collect()
26586}
26587
26588#[gpui::test]
26589fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26590    init_test(cx, |_| {});
26591
26592    let editor = cx.add_window(|window, cx| {
26593        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26594        build_editor(buffer, window, cx)
26595    });
26596
26597    editor
26598        .update(cx, |editor, window, cx| {
26599            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26600                s.select_display_ranges([
26601                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26602                ])
26603            });
26604
26605            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26606
26607            assert_eq!(
26608                editor.display_text(cx),
26609                "line1\nline2\nline2",
26610                "Duplicating last line upward should create duplicate above, not on same line"
26611            );
26612
26613            assert_eq!(
26614                editor.selections.display_ranges(cx),
26615                vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
26616                "Selection should remain on the original line"
26617            );
26618        })
26619        .unwrap();
26620}
26621
26622#[gpui::test]
26623async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26624    init_test(cx, |_| {});
26625
26626    let mut cx = EditorTestContext::new(cx).await;
26627
26628    cx.set_state("line1\nline2ˇ");
26629
26630    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26631
26632    let clipboard_text = cx
26633        .read_from_clipboard()
26634        .and_then(|item| item.text().as_deref().map(str::to_string));
26635
26636    assert_eq!(
26637        clipboard_text,
26638        Some("line2\n".to_string()),
26639        "Copying a line without trailing newline should include a newline"
26640    );
26641
26642    cx.set_state("line1\nˇ");
26643
26644    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26645
26646    cx.assert_editor_state("line1\nline2\nˇ");
26647}