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    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   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;
   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_item_view::InvalidItemView,
   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!(
  224            editor.selections.ranges(&editor.display_snapshot(cx)),
  225            vec![4..4]
  226        );
  227
  228        editor.start_transaction_at(now, window, cx);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([4..5])
  231        });
  232        editor.insert("e", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cde6");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![5..5]
  238        );
  239
  240        now += group_interval + Duration::from_millis(1);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([2..2])
  243        });
  244
  245        // Simulate an edit in another editor
  246        buffer.update(cx, |buffer, cx| {
  247            buffer.start_transaction_at(now, cx);
  248            buffer.edit([(0..1, "a")], None, cx);
  249            buffer.edit([(1..1, "b")], None, cx);
  250            buffer.end_transaction_at(now, cx);
  251        });
  252
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(
  255            editor.selections.ranges(&editor.display_snapshot(cx)),
  256            vec![3..3]
  257        );
  258
  259        // Last transaction happened past the group interval in a different editor.
  260        // Undo it individually and don't restore selections.
  261        editor.undo(&Undo, window, cx);
  262        assert_eq!(editor.text(cx), "12cde6");
  263        assert_eq!(
  264            editor.selections.ranges(&editor.display_snapshot(cx)),
  265            vec![2..2]
  266        );
  267
  268        // First two transactions happened within the group interval in this editor.
  269        // Undo them together and restore selections.
  270        editor.undo(&Undo, window, cx);
  271        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  272        assert_eq!(editor.text(cx), "123456");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![0..0]
  276        );
  277
  278        // Redo the first two transactions together.
  279        editor.redo(&Redo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![5..5]
  284        );
  285
  286        // Redo the last transaction on its own.
  287        editor.redo(&Redo, window, cx);
  288        assert_eq!(editor.text(cx), "ab2cde6");
  289        assert_eq!(
  290            editor.selections.ranges(&editor.display_snapshot(cx)),
  291            vec![6..6]
  292        );
  293
  294        // Test empty transactions.
  295        editor.start_transaction_at(now, window, cx);
  296        editor.end_transaction_at(now, cx);
  297        editor.undo(&Undo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299    });
  300}
  301
  302#[gpui::test]
  303fn test_ime_composition(cx: &mut TestAppContext) {
  304    init_test(cx, |_| {});
  305
  306    let buffer = cx.new(|cx| {
  307        let mut buffer = language::Buffer::local("abcde", cx);
  308        // Ensure automatic grouping doesn't occur.
  309        buffer.set_group_interval(Duration::ZERO);
  310        buffer
  311    });
  312
  313    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  314    cx.add_window(|window, cx| {
  315        let mut editor = build_editor(buffer.clone(), window, cx);
  316
  317        // Start a new IME composition.
  318        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  319        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  320        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  321        assert_eq!(editor.text(cx), "äbcde");
  322        assert_eq!(
  323            editor.marked_text_ranges(cx),
  324            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  325        );
  326
  327        // Finalize IME composition.
  328        editor.replace_text_in_range(None, "ā", window, cx);
  329        assert_eq!(editor.text(cx), "ābcde");
  330        assert_eq!(editor.marked_text_ranges(cx), None);
  331
  332        // IME composition edits are grouped and are undone/redone at once.
  333        editor.undo(&Default::default(), window, cx);
  334        assert_eq!(editor.text(cx), "abcde");
  335        assert_eq!(editor.marked_text_ranges(cx), None);
  336        editor.redo(&Default::default(), window, cx);
  337        assert_eq!(editor.text(cx), "ābcde");
  338        assert_eq!(editor.marked_text_ranges(cx), None);
  339
  340        // Start a new IME composition.
  341        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  345        );
  346
  347        // Undoing during an IME composition cancels it.
  348        editor.undo(&Default::default(), window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  353        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  354        assert_eq!(editor.text(cx), "ābcdè");
  355        assert_eq!(
  356            editor.marked_text_ranges(cx),
  357            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  358        );
  359
  360        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  361        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  362        assert_eq!(editor.text(cx), "ābcdę");
  363        assert_eq!(editor.marked_text_ranges(cx), None);
  364
  365        // Start a new IME composition with multiple cursors.
  366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  367            s.select_ranges([
  368                OffsetUtf16(1)..OffsetUtf16(1),
  369                OffsetUtf16(3)..OffsetUtf16(3),
  370                OffsetUtf16(5)..OffsetUtf16(5),
  371            ])
  372        });
  373        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  374        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  375        assert_eq!(
  376            editor.marked_text_ranges(cx),
  377            Some(vec![
  378                OffsetUtf16(0)..OffsetUtf16(3),
  379                OffsetUtf16(4)..OffsetUtf16(7),
  380                OffsetUtf16(8)..OffsetUtf16(11)
  381            ])
  382        );
  383
  384        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  385        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  386        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  387        assert_eq!(
  388            editor.marked_text_ranges(cx),
  389            Some(vec![
  390                OffsetUtf16(1)..OffsetUtf16(2),
  391                OffsetUtf16(5)..OffsetUtf16(6),
  392                OffsetUtf16(9)..OffsetUtf16(10)
  393            ])
  394        );
  395
  396        // Finalize IME composition with multiple cursors.
  397        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  398        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  399        assert_eq!(editor.marked_text_ranges(cx), None);
  400
  401        editor
  402    });
  403}
  404
  405#[gpui::test]
  406fn test_selection_with_mouse(cx: &mut TestAppContext) {
  407    init_test(cx, |_| {});
  408
  409    let editor = cx.add_window(|window, cx| {
  410        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  411        build_editor(buffer, window, cx)
  412    });
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  416    });
  417    assert_eq!(
  418        editor
  419            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  420            .unwrap(),
  421        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  422    );
  423
  424    _ = editor.update(cx, |editor, window, cx| {
  425        editor.update_selection(
  426            DisplayPoint::new(DisplayRow(3), 3),
  427            0,
  428            gpui::Point::<f32>::default(),
  429            window,
  430            cx,
  431        );
  432    });
  433
  434    assert_eq!(
  435        editor
  436            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  437            .unwrap(),
  438        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  439    );
  440
  441    _ = editor.update(cx, |editor, window, cx| {
  442        editor.update_selection(
  443            DisplayPoint::new(DisplayRow(1), 1),
  444            0,
  445            gpui::Point::<f32>::default(),
  446            window,
  447            cx,
  448        );
  449    });
  450
  451    assert_eq!(
  452        editor
  453            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  454            .unwrap(),
  455        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  456    );
  457
  458    _ = editor.update(cx, |editor, window, cx| {
  459        editor.end_selection(window, cx);
  460        editor.update_selection(
  461            DisplayPoint::new(DisplayRow(3), 3),
  462            0,
  463            gpui::Point::<f32>::default(),
  464            window,
  465            cx,
  466        );
  467    });
  468
  469    assert_eq!(
  470        editor
  471            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  472            .unwrap(),
  473        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  478        editor.update_selection(
  479            DisplayPoint::new(DisplayRow(0), 0),
  480            0,
  481            gpui::Point::<f32>::default(),
  482            window,
  483            cx,
  484        );
  485    });
  486
  487    assert_eq!(
  488        editor
  489            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  490            .unwrap(),
  491        [
  492            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  493            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  494        ]
  495    );
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.end_selection(window, cx);
  499    });
  500
  501    assert_eq!(
  502        editor
  503            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  504            .unwrap(),
  505        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  506    );
  507}
  508
  509#[gpui::test]
  510fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  511    init_test(cx, |_| {});
  512
  513    let editor = cx.add_window(|window, cx| {
  514        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  515        build_editor(buffer, window, cx)
  516    });
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  520    });
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  528    });
  529
  530    _ = editor.update(cx, |editor, window, cx| {
  531        editor.end_selection(window, cx);
  532    });
  533
  534    assert_eq!(
  535        editor
  536            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  537            .unwrap(),
  538        [
  539            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  540            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  541        ]
  542    );
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    assert_eq!(
  553        editor
  554            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  555            .unwrap(),
  556        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  557    );
  558}
  559
  560#[gpui::test]
  561fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  562    init_test(cx, |_| {});
  563
  564    let editor = cx.add_window(|window, cx| {
  565        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  566        build_editor(buffer, window, cx)
  567    });
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  574        );
  575    });
  576
  577    _ = editor.update(cx, |editor, window, cx| {
  578        editor.update_selection(
  579            DisplayPoint::new(DisplayRow(3), 3),
  580            0,
  581            gpui::Point::<f32>::default(),
  582            window,
  583            cx,
  584        );
  585        assert_eq!(
  586            editor.selections.display_ranges(cx),
  587            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  588        );
  589    });
  590
  591    _ = editor.update(cx, |editor, window, cx| {
  592        editor.cancel(&Cancel, window, cx);
  593        editor.update_selection(
  594            DisplayPoint::new(DisplayRow(1), 1),
  595            0,
  596            gpui::Point::<f32>::default(),
  597            window,
  598            cx,
  599        );
  600        assert_eq!(
  601            editor.selections.display_ranges(cx),
  602            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  603        );
  604    });
  605}
  606
  607#[gpui::test]
  608fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  609    init_test(cx, |_| {});
  610
  611    let editor = cx.add_window(|window, cx| {
  612        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  613        build_editor(buffer, window, cx)
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  618        assert_eq!(
  619            editor.selections.display_ranges(cx),
  620            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  621        );
  622
  623        editor.move_down(&Default::default(), window, cx);
  624        assert_eq!(
  625            editor.selections.display_ranges(cx),
  626            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  627        );
  628
  629        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  630        assert_eq!(
  631            editor.selections.display_ranges(cx),
  632            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  633        );
  634
  635        editor.move_up(&Default::default(), window, cx);
  636        assert_eq!(
  637            editor.selections.display_ranges(cx),
  638            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  639        );
  640    });
  641}
  642
  643#[gpui::test]
  644fn test_extending_selection(cx: &mut TestAppContext) {
  645    init_test(cx, |_| {});
  646
  647    let editor = cx.add_window(|window, cx| {
  648        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  649        build_editor(buffer, window, cx)
  650    });
  651
  652    _ = editor.update(cx, |editor, window, cx| {
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  654        editor.end_selection(window, cx);
  655        assert_eq!(
  656            editor.selections.display_ranges(cx),
  657            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  658        );
  659
  660        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  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), 10)]
  665        );
  666
  667        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  668        editor.end_selection(window, cx);
  669        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  670        assert_eq!(
  671            editor.selections.display_ranges(cx),
  672            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  673        );
  674
  675        editor.update_selection(
  676            DisplayPoint::new(DisplayRow(0), 1),
  677            0,
  678            gpui::Point::<f32>::default(),
  679            window,
  680            cx,
  681        );
  682        editor.end_selection(window, cx);
  683        assert_eq!(
  684            editor.selections.display_ranges(cx),
  685            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  686        );
  687
  688        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  689        editor.end_selection(window, cx);
  690        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  691        editor.end_selection(window, cx);
  692        assert_eq!(
  693            editor.selections.display_ranges(cx),
  694            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  695        );
  696
  697        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  698        assert_eq!(
  699            editor.selections.display_ranges(cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  701        );
  702
  703        editor.update_selection(
  704            DisplayPoint::new(DisplayRow(0), 6),
  705            0,
  706            gpui::Point::<f32>::default(),
  707            window,
  708            cx,
  709        );
  710        assert_eq!(
  711            editor.selections.display_ranges(cx),
  712            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  713        );
  714
  715        editor.update_selection(
  716            DisplayPoint::new(DisplayRow(0), 1),
  717            0,
  718            gpui::Point::<f32>::default(),
  719            window,
  720            cx,
  721        );
  722        editor.end_selection(window, cx);
  723        assert_eq!(
  724            editor.selections.display_ranges(cx),
  725            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  726        );
  727    });
  728}
  729
  730#[gpui::test]
  731fn test_clone(cx: &mut TestAppContext) {
  732    init_test(cx, |_| {});
  733
  734    let (text, selection_ranges) = marked_text_ranges(
  735        indoc! {"
  736            one
  737            two
  738            threeˇ
  739            four
  740            fiveˇ
  741        "},
  742        true,
  743    );
  744
  745    let editor = cx.add_window(|window, cx| {
  746        let buffer = MultiBuffer::build_simple(&text, cx);
  747        build_editor(buffer, window, cx)
  748    });
  749
  750    _ = editor.update(cx, |editor, window, cx| {
  751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  752            s.select_ranges(selection_ranges.clone())
  753        });
  754        editor.fold_creases(
  755            vec![
  756                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  757                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  758            ],
  759            true,
  760            window,
  761            cx,
  762        );
  763    });
  764
  765    let cloned_editor = editor
  766        .update(cx, |editor, _, cx| {
  767            cx.open_window(Default::default(), |window, cx| {
  768                cx.new(|cx| editor.clone(window, cx))
  769            })
  770        })
  771        .unwrap()
  772        .unwrap();
  773
  774    let snapshot = editor
  775        .update(cx, |e, window, cx| e.snapshot(window, cx))
  776        .unwrap();
  777    let cloned_snapshot = cloned_editor
  778        .update(cx, |e, window, cx| e.snapshot(window, cx))
  779        .unwrap();
  780
  781    assert_eq!(
  782        cloned_editor
  783            .update(cx, |e, _, cx| e.display_text(cx))
  784            .unwrap(),
  785        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  786    );
  787    assert_eq!(
  788        cloned_snapshot
  789            .folds_in_range(0..text.len())
  790            .collect::<Vec<_>>(),
  791        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  792    );
  793    assert_set_eq!(
  794        cloned_editor
  795            .update(cx, |editor, _, cx| editor
  796                .selections
  797                .ranges::<Point>(&editor.display_snapshot(cx)))
  798            .unwrap(),
  799        editor
  800            .update(cx, |editor, _, cx| editor
  801                .selections
  802                .ranges(&editor.display_snapshot(cx)))
  803            .unwrap()
  804    );
  805    assert_set_eq!(
  806        cloned_editor
  807            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  808            .unwrap(),
  809        editor
  810            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  811            .unwrap()
  812    );
  813}
  814
  815#[gpui::test]
  816async fn test_navigation_history(cx: &mut TestAppContext) {
  817    init_test(cx, |_| {});
  818
  819    use workspace::item::Item;
  820
  821    let fs = FakeFs::new(cx.executor());
  822    let project = Project::test(fs, [], cx).await;
  823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  824    let pane = workspace
  825        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  826        .unwrap();
  827
  828    _ = workspace.update(cx, |_v, window, cx| {
  829        cx.new(|cx| {
  830            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  831            let mut editor = build_editor(buffer, window, cx);
  832            let handle = cx.entity();
  833            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  834
  835            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  836                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  837            }
  838
  839            // Move the cursor a small distance.
  840            // Nothing is added to the navigation history.
  841            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  842                s.select_display_ranges([
  843                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  844                ])
  845            });
  846            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  847                s.select_display_ranges([
  848                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  849                ])
  850            });
  851            assert!(pop_history(&mut editor, cx).is_none());
  852
  853            // Move the cursor a large distance.
  854            // The history can jump back to the previous position.
  855            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  856                s.select_display_ranges([
  857                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  858                ])
  859            });
  860            let nav_entry = pop_history(&mut editor, cx).unwrap();
  861            editor.navigate(nav_entry.data.unwrap(), window, cx);
  862            assert_eq!(nav_entry.item.id(), cx.entity_id());
  863            assert_eq!(
  864                editor.selections.display_ranges(cx),
  865                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  866            );
  867            assert!(pop_history(&mut editor, cx).is_none());
  868
  869            // Move the cursor a small distance via the mouse.
  870            // Nothing is added to the navigation history.
  871            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  872            editor.end_selection(window, cx);
  873            assert_eq!(
  874                editor.selections.display_ranges(cx),
  875                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  876            );
  877            assert!(pop_history(&mut editor, cx).is_none());
  878
  879            // Move the cursor a large distance via the mouse.
  880            // The history can jump back to the previous position.
  881            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  882            editor.end_selection(window, cx);
  883            assert_eq!(
  884                editor.selections.display_ranges(cx),
  885                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  886            );
  887            let nav_entry = pop_history(&mut editor, cx).unwrap();
  888            editor.navigate(nav_entry.data.unwrap(), window, cx);
  889            assert_eq!(nav_entry.item.id(), cx.entity_id());
  890            assert_eq!(
  891                editor.selections.display_ranges(cx),
  892                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  893            );
  894            assert!(pop_history(&mut editor, cx).is_none());
  895
  896            // Set scroll position to check later
  897            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  898            let original_scroll_position = editor.scroll_manager.anchor();
  899
  900            // Jump to the end of the document and adjust scroll
  901            editor.move_to_end(&MoveToEnd, window, cx);
  902            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  903            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  904
  905            let nav_entry = pop_history(&mut editor, cx).unwrap();
  906            editor.navigate(nav_entry.data.unwrap(), window, cx);
  907            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  908
  909            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  910            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  911            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  912            let invalid_point = Point::new(9999, 0);
  913            editor.navigate(
  914                Box::new(NavigationData {
  915                    cursor_anchor: invalid_anchor,
  916                    cursor_position: invalid_point,
  917                    scroll_anchor: ScrollAnchor {
  918                        anchor: invalid_anchor,
  919                        offset: Default::default(),
  920                    },
  921                    scroll_top_row: invalid_point.row,
  922                }),
  923                window,
  924                cx,
  925            );
  926            assert_eq!(
  927                editor.selections.display_ranges(cx),
  928                &[editor.max_point(cx)..editor.max_point(cx)]
  929            );
  930            assert_eq!(
  931                editor.scroll_position(cx),
  932                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  933            );
  934
  935            editor
  936        })
  937    });
  938}
  939
  940#[gpui::test]
  941fn test_cancel(cx: &mut TestAppContext) {
  942    init_test(cx, |_| {});
  943
  944    let editor = cx.add_window(|window, cx| {
  945        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  946        build_editor(buffer, window, cx)
  947    });
  948
  949    _ = editor.update(cx, |editor, window, cx| {
  950        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  951        editor.update_selection(
  952            DisplayPoint::new(DisplayRow(1), 1),
  953            0,
  954            gpui::Point::<f32>::default(),
  955            window,
  956            cx,
  957        );
  958        editor.end_selection(window, cx);
  959
  960        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  961        editor.update_selection(
  962            DisplayPoint::new(DisplayRow(0), 3),
  963            0,
  964            gpui::Point::<f32>::default(),
  965            window,
  966            cx,
  967        );
  968        editor.end_selection(window, cx);
  969        assert_eq!(
  970            editor.selections.display_ranges(cx),
  971            [
  972                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  973                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  974            ]
  975        );
  976    });
  977
  978    _ = editor.update(cx, |editor, window, cx| {
  979        editor.cancel(&Cancel, window, cx);
  980        assert_eq!(
  981            editor.selections.display_ranges(cx),
  982            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  983        );
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.cancel(&Cancel, window, cx);
  988        assert_eq!(
  989            editor.selections.display_ranges(cx),
  990            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  991        );
  992    });
  993}
  994
  995#[gpui::test]
  996fn test_fold_action(cx: &mut TestAppContext) {
  997    init_test(cx, |_| {});
  998
  999    let editor = cx.add_window(|window, cx| {
 1000        let buffer = MultiBuffer::build_simple(
 1001            &"
 1002                impl Foo {
 1003                    // Hello!
 1004
 1005                    fn a() {
 1006                        1
 1007                    }
 1008
 1009                    fn b() {
 1010                        2
 1011                    }
 1012
 1013                    fn c() {
 1014                        3
 1015                    }
 1016                }
 1017            "
 1018            .unindent(),
 1019            cx,
 1020        );
 1021        build_editor(buffer, window, cx)
 1022    });
 1023
 1024    _ = editor.update(cx, |editor, window, cx| {
 1025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1026            s.select_display_ranges([
 1027                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1028            ]);
 1029        });
 1030        editor.fold(&Fold, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            "
 1034                impl Foo {
 1035                    // Hello!
 1036
 1037                    fn a() {
 1038                        1
 1039                    }
 1040
 1041                    fn b() {⋯
 1042                    }
 1043
 1044                    fn c() {⋯
 1045                    }
 1046                }
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.fold(&Fold, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            "
 1055                impl Foo {⋯
 1056                }
 1057            "
 1058            .unindent(),
 1059        );
 1060
 1061        editor.unfold_lines(&UnfoldLines, window, cx);
 1062        assert_eq!(
 1063            editor.display_text(cx),
 1064            "
 1065                impl Foo {
 1066                    // Hello!
 1067
 1068                    fn a() {
 1069                        1
 1070                    }
 1071
 1072                    fn b() {⋯
 1073                    }
 1074
 1075                    fn c() {⋯
 1076                    }
 1077                }
 1078            "
 1079            .unindent(),
 1080        );
 1081
 1082        editor.unfold_lines(&UnfoldLines, window, cx);
 1083        assert_eq!(
 1084            editor.display_text(cx),
 1085            editor.buffer.read(cx).read(cx).text()
 1086        );
 1087    });
 1088}
 1089
 1090#[gpui::test]
 1091fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1092    init_test(cx, |_| {});
 1093
 1094    let editor = cx.add_window(|window, cx| {
 1095        let buffer = MultiBuffer::build_simple(
 1096            &"
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():
 1104                        print(2)
 1105
 1106                    def c():
 1107                        print(3)
 1108            "
 1109            .unindent(),
 1110            cx,
 1111        );
 1112        build_editor(buffer, window, cx)
 1113    });
 1114
 1115    _ = editor.update(cx, |editor, window, cx| {
 1116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1117            s.select_display_ranges([
 1118                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1119            ]);
 1120        });
 1121        editor.fold(&Fold, window, cx);
 1122        assert_eq!(
 1123            editor.display_text(cx),
 1124            "
 1125                class Foo:
 1126                    # Hello!
 1127
 1128                    def a():
 1129                        print(1)
 1130
 1131                    def b():⋯
 1132
 1133                    def c():⋯
 1134            "
 1135            .unindent(),
 1136        );
 1137
 1138        editor.fold(&Fold, window, cx);
 1139        assert_eq!(
 1140            editor.display_text(cx),
 1141            "
 1142                class Foo:⋯
 1143            "
 1144            .unindent(),
 1145        );
 1146
 1147        editor.unfold_lines(&UnfoldLines, window, cx);
 1148        assert_eq!(
 1149            editor.display_text(cx),
 1150            "
 1151                class Foo:
 1152                    # Hello!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():⋯
 1158
 1159                    def c():⋯
 1160            "
 1161            .unindent(),
 1162        );
 1163
 1164        editor.unfold_lines(&UnfoldLines, window, cx);
 1165        assert_eq!(
 1166            editor.display_text(cx),
 1167            editor.buffer.read(cx).read(cx).text()
 1168        );
 1169    });
 1170}
 1171
 1172#[gpui::test]
 1173fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1174    init_test(cx, |_| {});
 1175
 1176    let editor = cx.add_window(|window, cx| {
 1177        let buffer = MultiBuffer::build_simple(
 1178            &"
 1179                class Foo:
 1180                    # Hello!
 1181
 1182                    def a():
 1183                        print(1)
 1184
 1185                    def b():
 1186                        print(2)
 1187
 1188
 1189                    def c():
 1190                        print(3)
 1191
 1192
 1193            "
 1194            .unindent(),
 1195            cx,
 1196        );
 1197        build_editor(buffer, window, cx)
 1198    });
 1199
 1200    _ = editor.update(cx, |editor, window, cx| {
 1201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1202            s.select_display_ranges([
 1203                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1204            ]);
 1205        });
 1206        editor.fold(&Fold, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218
 1219                    def c():⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:⋯
 1231
 1232
 1233            "
 1234            .unindent(),
 1235        );
 1236
 1237        editor.unfold_lines(&UnfoldLines, window, cx);
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            "
 1241                class Foo:
 1242                    # Hello!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():⋯
 1248
 1249
 1250                    def c():⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            editor.buffer.read(cx).read(cx).text()
 1261        );
 1262    });
 1263}
 1264
 1265#[gpui::test]
 1266fn test_fold_at_level(cx: &mut TestAppContext) {
 1267    init_test(cx, |_| {});
 1268
 1269    let editor = cx.add_window(|window, cx| {
 1270        let buffer = MultiBuffer::build_simple(
 1271            &"
 1272                class Foo:
 1273                    # Hello!
 1274
 1275                    def a():
 1276                        print(1)
 1277
 1278                    def b():
 1279                        print(2)
 1280
 1281
 1282                class Bar:
 1283                    # World!
 1284
 1285                    def a():
 1286                        print(1)
 1287
 1288                    def b():
 1289                        print(2)
 1290
 1291
 1292            "
 1293            .unindent(),
 1294            cx,
 1295        );
 1296        build_editor(buffer, window, cx)
 1297    });
 1298
 1299    _ = editor.update(cx, |editor, window, cx| {
 1300        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1301        assert_eq!(
 1302            editor.display_text(cx),
 1303            "
 1304                class Foo:
 1305                    # Hello!
 1306
 1307                    def a():⋯
 1308
 1309                    def b():⋯
 1310
 1311
 1312                class Bar:
 1313                    # World!
 1314
 1315                    def a():⋯
 1316
 1317                    def b():⋯
 1318
 1319
 1320            "
 1321            .unindent(),
 1322        );
 1323
 1324        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1325        assert_eq!(
 1326            editor.display_text(cx),
 1327            "
 1328                class Foo:⋯
 1329
 1330
 1331                class Bar:⋯
 1332
 1333
 1334            "
 1335            .unindent(),
 1336        );
 1337
 1338        editor.unfold_all(&UnfoldAll, window, cx);
 1339        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1340        assert_eq!(
 1341            editor.display_text(cx),
 1342            "
 1343                class Foo:
 1344                    # Hello!
 1345
 1346                    def a():
 1347                        print(1)
 1348
 1349                    def b():
 1350                        print(2)
 1351
 1352
 1353                class Bar:
 1354                    # World!
 1355
 1356                    def a():
 1357                        print(1)
 1358
 1359                    def b():
 1360                        print(2)
 1361
 1362
 1363            "
 1364            .unindent(),
 1365        );
 1366
 1367        assert_eq!(
 1368            editor.display_text(cx),
 1369            editor.buffer.read(cx).read(cx).text()
 1370        );
 1371        let (_, positions) = marked_text_ranges(
 1372            &"
 1373                       class Foo:
 1374                           # Hello!
 1375
 1376                           def a():
 1377                              print(1)
 1378
 1379                           def b():
 1380                               p«riˇ»nt(2)
 1381
 1382
 1383                       class Bar:
 1384                           # World!
 1385
 1386                           def a():
 1387                               «ˇprint(1)
 1388
 1389                           def b():
 1390                               print(2)»
 1391
 1392
 1393                   "
 1394            .unindent(),
 1395            true,
 1396        );
 1397
 1398        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1399            s.select_ranges(positions)
 1400        });
 1401
 1402        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1403        assert_eq!(
 1404            editor.display_text(cx),
 1405            "
 1406                class Foo:
 1407                    # Hello!
 1408
 1409                    def a():⋯
 1410
 1411                    def b():
 1412                        print(2)
 1413
 1414
 1415                class Bar:
 1416                    # World!
 1417
 1418                    def a():
 1419                        print(1)
 1420
 1421                    def b():
 1422                        print(2)
 1423
 1424
 1425            "
 1426            .unindent(),
 1427        );
 1428    });
 1429}
 1430
 1431#[gpui::test]
 1432fn test_move_cursor(cx: &mut TestAppContext) {
 1433    init_test(cx, |_| {});
 1434
 1435    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1436    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1437
 1438    buffer.update(cx, |buffer, cx| {
 1439        buffer.edit(
 1440            vec![
 1441                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1442                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1443            ],
 1444            None,
 1445            cx,
 1446        );
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1452        );
 1453
 1454        editor.move_down(&MoveDown, window, cx);
 1455        assert_eq!(
 1456            editor.selections.display_ranges(cx),
 1457            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1458        );
 1459
 1460        editor.move_right(&MoveRight, window, cx);
 1461        assert_eq!(
 1462            editor.selections.display_ranges(cx),
 1463            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1464        );
 1465
 1466        editor.move_left(&MoveLeft, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1470        );
 1471
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1476        );
 1477
 1478        editor.move_to_end(&MoveToEnd, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1482        );
 1483
 1484        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1488        );
 1489
 1490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1491            s.select_display_ranges([
 1492                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1493            ]);
 1494        });
 1495        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1499        );
 1500
 1501        editor.select_to_end(&SelectToEnd, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1505        );
 1506    });
 1507}
 1508
 1509#[gpui::test]
 1510fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1511    init_test(cx, |_| {});
 1512
 1513    let editor = cx.add_window(|window, cx| {
 1514        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1515        build_editor(buffer, window, cx)
 1516    });
 1517
 1518    assert_eq!('🟥'.len_utf8(), 4);
 1519    assert_eq!('α'.len_utf8(), 2);
 1520
 1521    _ = editor.update(cx, |editor, window, cx| {
 1522        editor.fold_creases(
 1523            vec![
 1524                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1525                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1526                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1527            ],
 1528            true,
 1529            window,
 1530            cx,
 1531        );
 1532        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1533
 1534        editor.move_right(&MoveRight, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(0, "🟥".len())]
 1538        );
 1539        editor.move_right(&MoveRight, window, cx);
 1540        assert_eq!(
 1541            editor.selections.display_ranges(cx),
 1542            &[empty_range(0, "🟥🟧".len())]
 1543        );
 1544        editor.move_right(&MoveRight, window, cx);
 1545        assert_eq!(
 1546            editor.selections.display_ranges(cx),
 1547            &[empty_range(0, "🟥🟧⋯".len())]
 1548        );
 1549
 1550        editor.move_down(&MoveDown, window, cx);
 1551        assert_eq!(
 1552            editor.selections.display_ranges(cx),
 1553            &[empty_range(1, "ab⋯e".len())]
 1554        );
 1555        editor.move_left(&MoveLeft, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[empty_range(1, "ab⋯".len())]
 1559        );
 1560        editor.move_left(&MoveLeft, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(1, "ab".len())]
 1564        );
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[empty_range(1, "a".len())]
 1569        );
 1570
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(2, "α".len())]
 1575        );
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[empty_range(2, "αβ".len())]
 1580        );
 1581        editor.move_right(&MoveRight, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[empty_range(2, "αβ⋯".len())]
 1585        );
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(
 1588            editor.selections.display_ranges(cx),
 1589            &[empty_range(2, "αβ⋯ε".len())]
 1590        );
 1591
 1592        editor.move_up(&MoveUp, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[empty_range(1, "ab⋯e".len())]
 1596        );
 1597        editor.move_down(&MoveDown, window, cx);
 1598        assert_eq!(
 1599            editor.selections.display_ranges(cx),
 1600            &[empty_range(2, "αβ⋯ε".len())]
 1601        );
 1602        editor.move_up(&MoveUp, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[empty_range(1, "ab⋯e".len())]
 1606        );
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[empty_range(0, "🟥🟧".len())]
 1612        );
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        assert_eq!(
 1615            editor.selections.display_ranges(cx),
 1616            &[empty_range(0, "🟥".len())]
 1617        );
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(
 1620            editor.selections.display_ranges(cx),
 1621            &[empty_range(0, "".len())]
 1622        );
 1623    });
 1624}
 1625
 1626#[gpui::test]
 1627fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1628    init_test(cx, |_| {});
 1629
 1630    let editor = cx.add_window(|window, cx| {
 1631        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1632        build_editor(buffer, window, cx)
 1633    });
 1634    _ = editor.update(cx, |editor, window, cx| {
 1635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1636            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1637        });
 1638
 1639        // moving above start of document should move selection to start of document,
 1640        // but the next move down should still be at the original goal_x
 1641        editor.move_up(&MoveUp, window, cx);
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[empty_range(0, "".len())]
 1645        );
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[empty_range(1, "abcd".len())]
 1651        );
 1652
 1653        editor.move_down(&MoveDown, window, cx);
 1654        assert_eq!(
 1655            editor.selections.display_ranges(cx),
 1656            &[empty_range(2, "αβγ".len())]
 1657        );
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[empty_range(3, "abcd".len())]
 1663        );
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1669        );
 1670
 1671        // moving past end of document should not change goal_x
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(
 1674            editor.selections.display_ranges(cx),
 1675            &[empty_range(5, "".len())]
 1676        );
 1677
 1678        editor.move_down(&MoveDown, window, cx);
 1679        assert_eq!(
 1680            editor.selections.display_ranges(cx),
 1681            &[empty_range(5, "".len())]
 1682        );
 1683
 1684        editor.move_up(&MoveUp, window, cx);
 1685        assert_eq!(
 1686            editor.selections.display_ranges(cx),
 1687            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1688        );
 1689
 1690        editor.move_up(&MoveUp, window, cx);
 1691        assert_eq!(
 1692            editor.selections.display_ranges(cx),
 1693            &[empty_range(3, "abcd".len())]
 1694        );
 1695
 1696        editor.move_up(&MoveUp, window, cx);
 1697        assert_eq!(
 1698            editor.selections.display_ranges(cx),
 1699            &[empty_range(2, "αβγ".len())]
 1700        );
 1701    });
 1702}
 1703
 1704#[gpui::test]
 1705fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1706    init_test(cx, |_| {});
 1707    let move_to_beg = MoveToBeginningOfLine {
 1708        stop_at_soft_wraps: true,
 1709        stop_at_indent: true,
 1710    };
 1711
 1712    let delete_to_beg = DeleteToBeginningOfLine {
 1713        stop_at_indent: false,
 1714    };
 1715
 1716    let move_to_end = MoveToEndOfLine {
 1717        stop_at_soft_wraps: true,
 1718    };
 1719
 1720    let editor = cx.add_window(|window, cx| {
 1721        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1722        build_editor(buffer, window, cx)
 1723    });
 1724    _ = editor.update(cx, |editor, window, cx| {
 1725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1726            s.select_display_ranges([
 1727                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1728                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1729            ]);
 1730        });
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1735        assert_eq!(
 1736            editor.selections.display_ranges(cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1740            ]
 1741        );
 1742    });
 1743
 1744    _ = editor.update(cx, |editor, window, cx| {
 1745        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1746        assert_eq!(
 1747            editor.selections.display_ranges(cx),
 1748            &[
 1749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1750                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1751            ]
 1752        );
 1753    });
 1754
 1755    _ = editor.update(cx, |editor, window, cx| {
 1756        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1757        assert_eq!(
 1758            editor.selections.display_ranges(cx),
 1759            &[
 1760                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1762            ]
 1763        );
 1764    });
 1765
 1766    _ = editor.update(cx, |editor, window, cx| {
 1767        editor.move_to_end_of_line(&move_to_end, window, cx);
 1768        assert_eq!(
 1769            editor.selections.display_ranges(cx),
 1770            &[
 1771                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1772                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1773            ]
 1774        );
 1775    });
 1776
 1777    // Moving to the end of line again is a no-op.
 1778    _ = editor.update(cx, |editor, window, cx| {
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            editor.selections.display_ranges(cx),
 1782            &[
 1783                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1784                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1785            ]
 1786        );
 1787    });
 1788
 1789    _ = editor.update(cx, |editor, window, cx| {
 1790        editor.move_left(&MoveLeft, window, cx);
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            editor.selections.display_ranges(cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            editor.selections.display_ranges(cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_beginning_of_line(
 1828            &SelectToBeginningOfLine {
 1829                stop_at_soft_wraps: true,
 1830                stop_at_indent: true,
 1831            },
 1832            window,
 1833            cx,
 1834        );
 1835        assert_eq!(
 1836            editor.selections.display_ranges(cx),
 1837            &[
 1838                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1839                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1840            ]
 1841        );
 1842    });
 1843
 1844    _ = editor.update(cx, |editor, window, cx| {
 1845        editor.select_to_end_of_line(
 1846            &SelectToEndOfLine {
 1847                stop_at_soft_wraps: true,
 1848            },
 1849            window,
 1850            cx,
 1851        );
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1856                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1857            ]
 1858        );
 1859    });
 1860
 1861    _ = editor.update(cx, |editor, window, cx| {
 1862        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1863        assert_eq!(editor.display_text(cx), "ab\n  de");
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1869            ]
 1870        );
 1871    });
 1872
 1873    _ = editor.update(cx, |editor, window, cx| {
 1874        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1875        assert_eq!(editor.display_text(cx), "\n");
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883    });
 1884}
 1885
 1886#[gpui::test]
 1887fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1888    init_test(cx, |_| {});
 1889    let move_to_beg = MoveToBeginningOfLine {
 1890        stop_at_soft_wraps: false,
 1891        stop_at_indent: false,
 1892    };
 1893
 1894    let move_to_end = MoveToEndOfLine {
 1895        stop_at_soft_wraps: false,
 1896    };
 1897
 1898    let editor = cx.add_window(|window, cx| {
 1899        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1900        build_editor(buffer, window, cx)
 1901    });
 1902
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.set_wrap_width(Some(140.0.into()), cx);
 1905
 1906        // We expect the following lines after wrapping
 1907        // ```
 1908        // thequickbrownfox
 1909        // jumpedoverthelazydo
 1910        // gs
 1911        // ```
 1912        // The final `gs` was soft-wrapped onto a new line.
 1913        assert_eq!(
 1914            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1915            editor.display_text(cx),
 1916        );
 1917
 1918        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1919        // Start the cursor at the `k` on the first line
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1923            ]);
 1924        });
 1925
 1926        // Moving to the beginning of the line should put us at the beginning of the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1930            editor.selections.display_ranges(cx)
 1931        );
 1932
 1933        // Moving to the end of the line should put us at the end of the line.
 1934        editor.move_to_end_of_line(&move_to_end, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1937            editor.selections.display_ranges(cx)
 1938        );
 1939
 1940        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1941        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1943            s.select_display_ranges([
 1944                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1945            ]);
 1946        });
 1947
 1948        // Moving to the beginning of the line should put us at the start of the second line of
 1949        // display text, i.e., the `j`.
 1950        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1951        assert_eq!(
 1952            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1953            editor.selections.display_ranges(cx)
 1954        );
 1955
 1956        // Moving to the beginning of the line again should be a no-op.
 1957        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1958        assert_eq!(
 1959            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1960            editor.selections.display_ranges(cx)
 1961        );
 1962
 1963        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1964        // next display line.
 1965        editor.move_to_end_of_line(&move_to_end, window, cx);
 1966        assert_eq!(
 1967            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1968            editor.selections.display_ranges(cx)
 1969        );
 1970
 1971        // Moving to the end of the line again should be a no-op.
 1972        editor.move_to_end_of_line(&move_to_end, window, cx);
 1973        assert_eq!(
 1974            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1975            editor.selections.display_ranges(cx)
 1976        );
 1977    });
 1978}
 1979
 1980#[gpui::test]
 1981fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1982    init_test(cx, |_| {});
 1983
 1984    let move_to_beg = MoveToBeginningOfLine {
 1985        stop_at_soft_wraps: true,
 1986        stop_at_indent: true,
 1987    };
 1988
 1989    let select_to_beg = SelectToBeginningOfLine {
 1990        stop_at_soft_wraps: true,
 1991        stop_at_indent: true,
 1992    };
 1993
 1994    let delete_to_beg = DeleteToBeginningOfLine {
 1995        stop_at_indent: true,
 1996    };
 1997
 1998    let move_to_end = MoveToEndOfLine {
 1999        stop_at_soft_wraps: false,
 2000    };
 2001
 2002    let editor = cx.add_window(|window, cx| {
 2003        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2004        build_editor(buffer, window, cx)
 2005    });
 2006
 2007    _ = editor.update(cx, |editor, window, cx| {
 2008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2009            s.select_display_ranges([
 2010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2011                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2012            ]);
 2013        });
 2014
 2015        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2016        // and the second cursor at the first non-whitespace character in the line.
 2017        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2018        assert_eq!(
 2019            editor.selections.display_ranges(cx),
 2020            &[
 2021                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2022                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2023            ]
 2024        );
 2025
 2026        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2027        // and should move the second cursor to the beginning of the line.
 2028        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2029        assert_eq!(
 2030            editor.selections.display_ranges(cx),
 2031            &[
 2032                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2033                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2034            ]
 2035        );
 2036
 2037        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2038        // and should move the second cursor back to the first non-whitespace character in the line.
 2039        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2040        assert_eq!(
 2041            editor.selections.display_ranges(cx),
 2042            &[
 2043                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2044                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2045            ]
 2046        );
 2047
 2048        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2049        // and to the first non-whitespace character in the line for the second cursor.
 2050        editor.move_to_end_of_line(&move_to_end, window, cx);
 2051        editor.move_left(&MoveLeft, window, cx);
 2052        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060
 2061        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2062        // and should select to the beginning of the line for the second cursor.
 2063        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2064        assert_eq!(
 2065            editor.selections.display_ranges(cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2068                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2069            ]
 2070        );
 2071
 2072        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2073        // and should delete to the first non-whitespace character in the line for the second cursor.
 2074        editor.move_to_end_of_line(&move_to_end, window, cx);
 2075        editor.move_left(&MoveLeft, window, cx);
 2076        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2077        assert_eq!(editor.text(cx), "c\n  f");
 2078    });
 2079}
 2080
 2081#[gpui::test]
 2082fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2083    init_test(cx, |_| {});
 2084
 2085    let move_to_beg = MoveToBeginningOfLine {
 2086        stop_at_soft_wraps: true,
 2087        stop_at_indent: true,
 2088    };
 2089
 2090    let editor = cx.add_window(|window, cx| {
 2091        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2092        build_editor(buffer, window, cx)
 2093    });
 2094
 2095    _ = editor.update(cx, |editor, window, cx| {
 2096        // test cursor between line_start and indent_start
 2097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2098            s.select_display_ranges([
 2099                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2100            ]);
 2101        });
 2102
 2103        // cursor should move to line_start
 2104        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2105        assert_eq!(
 2106            editor.selections.display_ranges(cx),
 2107            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2108        );
 2109
 2110        // cursor should move to indent_start
 2111        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2115        );
 2116
 2117        // cursor should move to back to line_start
 2118        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2119        assert_eq!(
 2120            editor.selections.display_ranges(cx),
 2121            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2122        );
 2123    });
 2124}
 2125
 2126#[gpui::test]
 2127fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2128    init_test(cx, |_| {});
 2129
 2130    let editor = cx.add_window(|window, cx| {
 2131        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2132        build_editor(buffer, window, cx)
 2133    });
 2134    _ = editor.update(cx, |editor, window, cx| {
 2135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2136            s.select_display_ranges([
 2137                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2138                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2139            ])
 2140        });
 2141        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2142        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2143
 2144        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2145        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2148        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2149
 2150        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2151        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2152
 2153        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2154        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2158
 2159        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2160        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2164
 2165        editor.move_right(&MoveRight, window, cx);
 2166        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2167        assert_selection_ranges(
 2168            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2169            editor,
 2170            cx,
 2171        );
 2172
 2173        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2174        assert_selection_ranges(
 2175            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2176            editor,
 2177            cx,
 2178        );
 2179
 2180        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2181        assert_selection_ranges(
 2182            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2183            editor,
 2184            cx,
 2185        );
 2186    });
 2187}
 2188
 2189#[gpui::test]
 2190fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192
 2193    let editor = cx.add_window(|window, cx| {
 2194        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2195        build_editor(buffer, window, cx)
 2196    });
 2197
 2198    _ = editor.update(cx, |editor, window, cx| {
 2199        editor.set_wrap_width(Some(140.0.into()), cx);
 2200        assert_eq!(
 2201            editor.display_text(cx),
 2202            "use one::{\n    two::three::\n    four::five\n};"
 2203        );
 2204
 2205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2206            s.select_display_ranges([
 2207                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2208            ]);
 2209        });
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            editor.selections.display_ranges(cx),
 2214            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2215        );
 2216
 2217        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2218        assert_eq!(
 2219            editor.selections.display_ranges(cx),
 2220            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2221        );
 2222
 2223        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2224        assert_eq!(
 2225            editor.selections.display_ranges(cx),
 2226            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2227        );
 2228
 2229        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2230        assert_eq!(
 2231            editor.selections.display_ranges(cx),
 2232            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2233        );
 2234
 2235        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2236        assert_eq!(
 2237            editor.selections.display_ranges(cx),
 2238            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2239        );
 2240
 2241        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2242        assert_eq!(
 2243            editor.selections.display_ranges(cx),
 2244            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2245        );
 2246    });
 2247}
 2248
 2249#[gpui::test]
 2250async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2251    init_test(cx, |_| {});
 2252    let mut cx = EditorTestContext::new(cx).await;
 2253
 2254    let line_height = cx.editor(|editor, window, _| {
 2255        editor
 2256            .style()
 2257            .unwrap()
 2258            .text
 2259            .line_height_in_pixels(window.rem_size())
 2260    });
 2261    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2262
 2263    cx.set_state(
 2264        &r#"ˇone
 2265        two
 2266
 2267        three
 2268        fourˇ
 2269        five
 2270
 2271        six"#
 2272            .unindent(),
 2273    );
 2274
 2275    cx.update_editor(|editor, window, cx| {
 2276        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2277    });
 2278    cx.assert_editor_state(
 2279        &r#"one
 2280        two
 2281        ˇ
 2282        three
 2283        four
 2284        five
 2285        ˇ
 2286        six"#
 2287            .unindent(),
 2288    );
 2289
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2292    });
 2293    cx.assert_editor_state(
 2294        &r#"one
 2295        two
 2296
 2297        three
 2298        four
 2299        five
 2300        ˇ
 2301        sixˇ"#
 2302            .unindent(),
 2303    );
 2304
 2305    cx.update_editor(|editor, window, cx| {
 2306        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2307    });
 2308    cx.assert_editor_state(
 2309        &r#"one
 2310        two
 2311
 2312        three
 2313        four
 2314        five
 2315
 2316        sixˇ"#
 2317            .unindent(),
 2318    );
 2319
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2322    });
 2323    cx.assert_editor_state(
 2324        &r#"one
 2325        two
 2326
 2327        three
 2328        four
 2329        five
 2330        ˇ
 2331        six"#
 2332            .unindent(),
 2333    );
 2334
 2335    cx.update_editor(|editor, window, cx| {
 2336        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2337    });
 2338    cx.assert_editor_state(
 2339        &r#"one
 2340        two
 2341        ˇ
 2342        three
 2343        four
 2344        five
 2345
 2346        six"#
 2347            .unindent(),
 2348    );
 2349
 2350    cx.update_editor(|editor, window, cx| {
 2351        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2352    });
 2353    cx.assert_editor_state(
 2354        &r#"ˇone
 2355        two
 2356
 2357        three
 2358        four
 2359        five
 2360
 2361        six"#
 2362            .unindent(),
 2363    );
 2364}
 2365
 2366#[gpui::test]
 2367async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2368    init_test(cx, |_| {});
 2369    let mut cx = EditorTestContext::new(cx).await;
 2370    let line_height = cx.editor(|editor, window, _| {
 2371        editor
 2372            .style()
 2373            .unwrap()
 2374            .text
 2375            .line_height_in_pixels(window.rem_size())
 2376    });
 2377    let window = cx.window;
 2378    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2379
 2380    cx.set_state(
 2381        r#"ˇone
 2382        two
 2383        three
 2384        four
 2385        five
 2386        six
 2387        seven
 2388        eight
 2389        nine
 2390        ten
 2391        "#,
 2392    );
 2393
 2394    cx.update_editor(|editor, window, cx| {
 2395        assert_eq!(
 2396            editor.snapshot(window, cx).scroll_position(),
 2397            gpui::Point::new(0., 0.)
 2398        );
 2399        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2400        assert_eq!(
 2401            editor.snapshot(window, cx).scroll_position(),
 2402            gpui::Point::new(0., 3.)
 2403        );
 2404        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2405        assert_eq!(
 2406            editor.snapshot(window, cx).scroll_position(),
 2407            gpui::Point::new(0., 6.)
 2408        );
 2409        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2410        assert_eq!(
 2411            editor.snapshot(window, cx).scroll_position(),
 2412            gpui::Point::new(0., 3.)
 2413        );
 2414
 2415        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2416        assert_eq!(
 2417            editor.snapshot(window, cx).scroll_position(),
 2418            gpui::Point::new(0., 1.)
 2419        );
 2420        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2421        assert_eq!(
 2422            editor.snapshot(window, cx).scroll_position(),
 2423            gpui::Point::new(0., 3.)
 2424        );
 2425    });
 2426}
 2427
 2428#[gpui::test]
 2429async fn test_autoscroll(cx: &mut TestAppContext) {
 2430    init_test(cx, |_| {});
 2431    let mut cx = EditorTestContext::new(cx).await;
 2432
 2433    let line_height = cx.update_editor(|editor, window, cx| {
 2434        editor.set_vertical_scroll_margin(2, cx);
 2435        editor
 2436            .style()
 2437            .unwrap()
 2438            .text
 2439            .line_height_in_pixels(window.rem_size())
 2440    });
 2441    let window = cx.window;
 2442    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2443
 2444    cx.set_state(
 2445        r#"ˇone
 2446            two
 2447            three
 2448            four
 2449            five
 2450            six
 2451            seven
 2452            eight
 2453            nine
 2454            ten
 2455        "#,
 2456    );
 2457    cx.update_editor(|editor, window, cx| {
 2458        assert_eq!(
 2459            editor.snapshot(window, cx).scroll_position(),
 2460            gpui::Point::new(0., 0.0)
 2461        );
 2462    });
 2463
 2464    // Add a cursor below the visible area. Since both cursors cannot fit
 2465    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2466    // allows the vertical scroll margin below that cursor.
 2467    cx.update_editor(|editor, window, cx| {
 2468        editor.change_selections(Default::default(), window, cx, |selections| {
 2469            selections.select_ranges([
 2470                Point::new(0, 0)..Point::new(0, 0),
 2471                Point::new(6, 0)..Point::new(6, 0),
 2472            ]);
 2473        })
 2474    });
 2475    cx.update_editor(|editor, window, cx| {
 2476        assert_eq!(
 2477            editor.snapshot(window, cx).scroll_position(),
 2478            gpui::Point::new(0., 3.0)
 2479        );
 2480    });
 2481
 2482    // Move down. The editor cursor scrolls down to track the newest cursor.
 2483    cx.update_editor(|editor, window, cx| {
 2484        editor.move_down(&Default::default(), window, cx);
 2485    });
 2486    cx.update_editor(|editor, window, cx| {
 2487        assert_eq!(
 2488            editor.snapshot(window, cx).scroll_position(),
 2489            gpui::Point::new(0., 4.0)
 2490        );
 2491    });
 2492
 2493    // Add a cursor above the visible area. Since both cursors fit on screen,
 2494    // the editor scrolls to show both.
 2495    cx.update_editor(|editor, window, cx| {
 2496        editor.change_selections(Default::default(), window, cx, |selections| {
 2497            selections.select_ranges([
 2498                Point::new(1, 0)..Point::new(1, 0),
 2499                Point::new(6, 0)..Point::new(6, 0),
 2500            ]);
 2501        })
 2502    });
 2503    cx.update_editor(|editor, window, cx| {
 2504        assert_eq!(
 2505            editor.snapshot(window, cx).scroll_position(),
 2506            gpui::Point::new(0., 1.0)
 2507        );
 2508    });
 2509}
 2510
 2511#[gpui::test]
 2512async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2513    init_test(cx, |_| {});
 2514    let mut cx = EditorTestContext::new(cx).await;
 2515
 2516    let line_height = cx.editor(|editor, window, _cx| {
 2517        editor
 2518            .style()
 2519            .unwrap()
 2520            .text
 2521            .line_height_in_pixels(window.rem_size())
 2522    });
 2523    let window = cx.window;
 2524    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2525    cx.set_state(
 2526        &r#"
 2527        ˇone
 2528        two
 2529        threeˇ
 2530        four
 2531        five
 2532        six
 2533        seven
 2534        eight
 2535        nine
 2536        ten
 2537        "#
 2538        .unindent(),
 2539    );
 2540
 2541    cx.update_editor(|editor, window, cx| {
 2542        editor.move_page_down(&MovePageDown::default(), window, cx)
 2543    });
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| {
 2561        editor.move_page_down(&MovePageDown::default(), window, cx)
 2562    });
 2563    cx.assert_editor_state(
 2564        &r#"
 2565        one
 2566        two
 2567        three
 2568        four
 2569        five
 2570        six
 2571        ˇseven
 2572        eight
 2573        nineˇ
 2574        ten
 2575        "#
 2576        .unindent(),
 2577    );
 2578
 2579    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2580    cx.assert_editor_state(
 2581        &r#"
 2582        one
 2583        two
 2584        three
 2585        ˇfour
 2586        five
 2587        sixˇ
 2588        seven
 2589        eight
 2590        nine
 2591        ten
 2592        "#
 2593        .unindent(),
 2594    );
 2595
 2596    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        ˇone
 2600        two
 2601        threeˇ
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ten
 2609        "#
 2610        .unindent(),
 2611    );
 2612
 2613    // Test select collapsing
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.move_page_down(&MovePageDown::default(), window, cx);
 2616        editor.move_page_down(&MovePageDown::default(), window, cx);
 2617        editor.move_page_down(&MovePageDown::default(), window, cx);
 2618    });
 2619    cx.assert_editor_state(
 2620        &r#"
 2621        one
 2622        two
 2623        three
 2624        four
 2625        five
 2626        six
 2627        seven
 2628        eight
 2629        nine
 2630        ˇten
 2631        ˇ"#
 2632        .unindent(),
 2633    );
 2634}
 2635
 2636#[gpui::test]
 2637async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2638    init_test(cx, |_| {});
 2639    let mut cx = EditorTestContext::new(cx).await;
 2640    cx.set_state("one «two threeˇ» four");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_beginning_of_line(
 2643            &DeleteToBeginningOfLine {
 2644                stop_at_indent: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649        assert_eq!(editor.text(cx), " four");
 2650    });
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    // For an empty selection, the preceding word fragment is deleted.
 2660    // For non-empty selections, only selected characters are deleted.
 2661    cx.set_state("onˇe two t«hreˇ»e four");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: false,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    cx.assert_editor_state("ˇe two tˇe four");
 2673
 2674    cx.set_state("e tˇwo te «fˇ»our");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("e tˇ te ˇour");
 2686}
 2687
 2688#[gpui::test]
 2689async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2690    init_test(cx, |_| {});
 2691
 2692    let mut cx = EditorTestContext::new(cx).await;
 2693
 2694    cx.set_state("here is some text    ˇwith a space");
 2695    cx.update_editor(|editor, window, cx| {
 2696        editor.delete_to_previous_word_start(
 2697            &DeleteToPreviousWordStart {
 2698                ignore_newlines: false,
 2699                ignore_brackets: true,
 2700            },
 2701            window,
 2702            cx,
 2703        );
 2704    });
 2705    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2706    cx.assert_editor_state("here is some textˇwith a space");
 2707
 2708    cx.set_state("here is some text    ˇwith a space");
 2709    cx.update_editor(|editor, window, cx| {
 2710        editor.delete_to_previous_word_start(
 2711            &DeleteToPreviousWordStart {
 2712                ignore_newlines: false,
 2713                ignore_brackets: false,
 2714            },
 2715            window,
 2716            cx,
 2717        );
 2718    });
 2719    cx.assert_editor_state("here is some textˇwith a space");
 2720
 2721    cx.set_state("here is some textˇ    with a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_next_word_end(
 2724            &DeleteToNextWordEnd {
 2725                ignore_newlines: false,
 2726                ignore_brackets: true,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    // Same happens in the other direction.
 2733    cx.assert_editor_state("here is some textˇwith a space");
 2734
 2735    cx.set_state("here is some textˇ    with a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_next_word_end(
 2738            &DeleteToNextWordEnd {
 2739                ignore_newlines: false,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    cx.assert_editor_state("here is some textˇwith a space");
 2747
 2748    cx.set_state("here is some textˇ    with a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_next_word_end(
 2751            &DeleteToNextWordEnd {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("here is some textˇwith a space");
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    cx.assert_editor_state("here is some ˇwith a space");
 2771    cx.update_editor(|editor, window, cx| {
 2772        editor.delete_to_previous_word_start(
 2773            &DeleteToPreviousWordStart {
 2774                ignore_newlines: true,
 2775                ignore_brackets: false,
 2776            },
 2777            window,
 2778            cx,
 2779        );
 2780    });
 2781    // Single whitespaces are removed with the word behind them.
 2782    cx.assert_editor_state("here is ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("here ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_previous_word_start(
 2796            &DeleteToPreviousWordStart {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    cx.assert_editor_state("ˇwith a space");
 2805    cx.update_editor(|editor, window, cx| {
 2806        editor.delete_to_previous_word_start(
 2807            &DeleteToPreviousWordStart {
 2808                ignore_newlines: true,
 2809                ignore_brackets: false,
 2810            },
 2811            window,
 2812            cx,
 2813        );
 2814    });
 2815    cx.assert_editor_state("ˇwith a space");
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    // Same happens in the other direction.
 2827    cx.assert_editor_state("ˇ a space");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ space");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_next_word_end(
 2841            &DeleteToNextWordEnd {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850    cx.update_editor(|editor, window, cx| {
 2851        editor.delete_to_next_word_end(
 2852            &DeleteToNextWordEnd {
 2853                ignore_newlines: true,
 2854                ignore_brackets: false,
 2855            },
 2856            window,
 2857            cx,
 2858        );
 2859    });
 2860    cx.assert_editor_state("ˇ");
 2861    cx.update_editor(|editor, window, cx| {
 2862        editor.delete_to_previous_word_start(
 2863            &DeleteToPreviousWordStart {
 2864                ignore_newlines: true,
 2865                ignore_brackets: false,
 2866            },
 2867            window,
 2868            cx,
 2869        );
 2870    });
 2871    cx.assert_editor_state("ˇ");
 2872}
 2873
 2874#[gpui::test]
 2875async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2876    init_test(cx, |_| {});
 2877
 2878    let language = Arc::new(
 2879        Language::new(
 2880            LanguageConfig {
 2881                brackets: BracketPairConfig {
 2882                    pairs: vec![
 2883                        BracketPair {
 2884                            start: "\"".to_string(),
 2885                            end: "\"".to_string(),
 2886                            close: true,
 2887                            surround: true,
 2888                            newline: false,
 2889                        },
 2890                        BracketPair {
 2891                            start: "(".to_string(),
 2892                            end: ")".to_string(),
 2893                            close: true,
 2894                            surround: true,
 2895                            newline: true,
 2896                        },
 2897                    ],
 2898                    ..BracketPairConfig::default()
 2899                },
 2900                ..LanguageConfig::default()
 2901            },
 2902            Some(tree_sitter_rust::LANGUAGE.into()),
 2903        )
 2904        .with_brackets_query(
 2905            r#"
 2906                ("(" @open ")" @close)
 2907                ("\"" @open "\"" @close)
 2908            "#,
 2909        )
 2910        .unwrap(),
 2911    );
 2912
 2913    let mut cx = EditorTestContext::new(cx).await;
 2914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2915
 2916    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 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    // Deletion stops before brackets if asked to not ignore them.
 2928    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 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    // Deletion has to remove a single bracket and then stop again.
 2940    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_previous_word_start(
 2944            &DeleteToPreviousWordStart {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2953
 2954    cx.update_editor(|editor, window, cx| {
 2955        editor.delete_to_previous_word_start(
 2956            &DeleteToPreviousWordStart {
 2957                ignore_newlines: true,
 2958                ignore_brackets: false,
 2959            },
 2960            window,
 2961            cx,
 2962        );
 2963    });
 2964    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_previous_word_start(
 2968            &DeleteToPreviousWordStart {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 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    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2989    cx.assert_editor_state(r#"ˇ");"#);
 2990
 2991    cx.update_editor(|editor, window, cx| {
 2992        editor.delete_to_next_word_end(
 2993            &DeleteToNextWordEnd {
 2994                ignore_newlines: true,
 2995                ignore_brackets: false,
 2996            },
 2997            window,
 2998            cx,
 2999        );
 3000    });
 3001    cx.assert_editor_state(r#"ˇ"#);
 3002
 3003    cx.update_editor(|editor, window, cx| {
 3004        editor.delete_to_next_word_end(
 3005            &DeleteToNextWordEnd {
 3006                ignore_newlines: true,
 3007                ignore_brackets: false,
 3008            },
 3009            window,
 3010            cx,
 3011        );
 3012    });
 3013    cx.assert_editor_state(r#"ˇ"#);
 3014
 3015    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3016    cx.update_editor(|editor, window, cx| {
 3017        editor.delete_to_previous_word_start(
 3018            &DeleteToPreviousWordStart {
 3019                ignore_newlines: true,
 3020                ignore_brackets: true,
 3021            },
 3022            window,
 3023            cx,
 3024        );
 3025    });
 3026    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3027}
 3028
 3029#[gpui::test]
 3030fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3031    init_test(cx, |_| {});
 3032
 3033    let editor = cx.add_window(|window, cx| {
 3034        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3035        build_editor(buffer, window, cx)
 3036    });
 3037    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3038        ignore_newlines: false,
 3039        ignore_brackets: false,
 3040    };
 3041    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3042        ignore_newlines: true,
 3043        ignore_brackets: false,
 3044    };
 3045
 3046    _ = editor.update(cx, |editor, window, cx| {
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_display_ranges([
 3049                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3050            ])
 3051        });
 3052        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3053        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3054        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3056        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3058        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3060        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3062        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3064    });
 3065}
 3066
 3067#[gpui::test]
 3068fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3069    init_test(cx, |_| {});
 3070
 3071    let editor = cx.add_window(|window, cx| {
 3072        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3073        build_editor(buffer, window, cx)
 3074    });
 3075    let del_to_next_word_end = DeleteToNextWordEnd {
 3076        ignore_newlines: false,
 3077        ignore_brackets: false,
 3078    };
 3079    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3080        ignore_newlines: true,
 3081        ignore_brackets: false,
 3082    };
 3083
 3084    _ = editor.update(cx, |editor, window, cx| {
 3085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3086            s.select_display_ranges([
 3087                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3088            ])
 3089        });
 3090        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3091        assert_eq!(
 3092            editor.buffer.read(cx).read(cx).text(),
 3093            "one\n   two\nthree\n   four"
 3094        );
 3095        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3096        assert_eq!(
 3097            editor.buffer.read(cx).read(cx).text(),
 3098            "\n   two\nthree\n   four"
 3099        );
 3100        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3101        assert_eq!(
 3102            editor.buffer.read(cx).read(cx).text(),
 3103            "two\nthree\n   four"
 3104        );
 3105        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3106        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3107        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3108        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3109        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3110        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3111        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3113    });
 3114}
 3115
 3116#[gpui::test]
 3117fn test_newline(cx: &mut TestAppContext) {
 3118    init_test(cx, |_| {});
 3119
 3120    let editor = cx.add_window(|window, cx| {
 3121        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3122        build_editor(buffer, window, cx)
 3123    });
 3124
 3125    _ = editor.update(cx, |editor, window, cx| {
 3126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3127            s.select_display_ranges([
 3128                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3129                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3130                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3131            ])
 3132        });
 3133
 3134        editor.newline(&Newline, window, cx);
 3135        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3136    });
 3137}
 3138
 3139#[gpui::test]
 3140fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let editor = cx.add_window(|window, cx| {
 3144        let buffer = MultiBuffer::build_simple(
 3145            "
 3146                a
 3147                b(
 3148                    X
 3149                )
 3150                c(
 3151                    X
 3152                )
 3153            "
 3154            .unindent()
 3155            .as_str(),
 3156            cx,
 3157        );
 3158        let mut editor = build_editor(buffer, window, cx);
 3159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3160            s.select_ranges([
 3161                Point::new(2, 4)..Point::new(2, 5),
 3162                Point::new(5, 4)..Point::new(5, 5),
 3163            ])
 3164        });
 3165        editor
 3166    });
 3167
 3168    _ = editor.update(cx, |editor, window, cx| {
 3169        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3170        editor.buffer.update(cx, |buffer, cx| {
 3171            buffer.edit(
 3172                [
 3173                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3174                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3175                ],
 3176                None,
 3177                cx,
 3178            );
 3179            assert_eq!(
 3180                buffer.read(cx).text(),
 3181                "
 3182                    a
 3183                    b()
 3184                    c()
 3185                "
 3186                .unindent()
 3187            );
 3188        });
 3189        assert_eq!(
 3190            editor.selections.ranges(&editor.display_snapshot(cx)),
 3191            &[
 3192                Point::new(1, 2)..Point::new(1, 2),
 3193                Point::new(2, 2)..Point::new(2, 2),
 3194            ],
 3195        );
 3196
 3197        editor.newline(&Newline, window, cx);
 3198        assert_eq!(
 3199            editor.text(cx),
 3200            "
 3201                a
 3202                b(
 3203                )
 3204                c(
 3205                )
 3206            "
 3207            .unindent()
 3208        );
 3209
 3210        // The selections are moved after the inserted newlines
 3211        assert_eq!(
 3212            editor.selections.ranges(&editor.display_snapshot(cx)),
 3213            &[
 3214                Point::new(2, 0)..Point::new(2, 0),
 3215                Point::new(4, 0)..Point::new(4, 0),
 3216            ],
 3217        );
 3218    });
 3219}
 3220
 3221#[gpui::test]
 3222async fn test_newline_above(cx: &mut TestAppContext) {
 3223    init_test(cx, |settings| {
 3224        settings.defaults.tab_size = NonZeroU32::new(4)
 3225    });
 3226
 3227    let language = Arc::new(
 3228        Language::new(
 3229            LanguageConfig::default(),
 3230            Some(tree_sitter_rust::LANGUAGE.into()),
 3231        )
 3232        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3233        .unwrap(),
 3234    );
 3235
 3236    let mut cx = EditorTestContext::new(cx).await;
 3237    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3238    cx.set_state(indoc! {"
 3239        const a: ˇA = (
 3240 3241                «const_functionˇ»(ˇ),
 3242                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3243 3244        ˇ);ˇ
 3245    "});
 3246
 3247    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3248    cx.assert_editor_state(indoc! {"
 3249        ˇ
 3250        const a: A = (
 3251            ˇ
 3252            (
 3253                ˇ
 3254                ˇ
 3255                const_function(),
 3256                ˇ
 3257                ˇ
 3258                ˇ
 3259                ˇ
 3260                something_else,
 3261                ˇ
 3262            )
 3263            ˇ
 3264            ˇ
 3265        );
 3266    "});
 3267}
 3268
 3269#[gpui::test]
 3270async fn test_newline_below(cx: &mut TestAppContext) {
 3271    init_test(cx, |settings| {
 3272        settings.defaults.tab_size = NonZeroU32::new(4)
 3273    });
 3274
 3275    let language = Arc::new(
 3276        Language::new(
 3277            LanguageConfig::default(),
 3278            Some(tree_sitter_rust::LANGUAGE.into()),
 3279        )
 3280        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3281        .unwrap(),
 3282    );
 3283
 3284    let mut cx = EditorTestContext::new(cx).await;
 3285    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3286    cx.set_state(indoc! {"
 3287        const a: ˇA = (
 3288 3289                «const_functionˇ»(ˇ),
 3290                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3291 3292        ˇ);ˇ
 3293    "});
 3294
 3295    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: A = (
 3298            ˇ
 3299            (
 3300                ˇ
 3301                const_function(),
 3302                ˇ
 3303                ˇ
 3304                something_else,
 3305                ˇ
 3306                ˇ
 3307                ˇ
 3308                ˇ
 3309            )
 3310            ˇ
 3311        );
 3312        ˇ
 3313        ˇ
 3314    "});
 3315}
 3316
 3317#[gpui::test]
 3318async fn test_newline_comments(cx: &mut TestAppContext) {
 3319    init_test(cx, |settings| {
 3320        settings.defaults.tab_size = NonZeroU32::new(4)
 3321    });
 3322
 3323    let language = Arc::new(Language::new(
 3324        LanguageConfig {
 3325            line_comments: vec!["// ".into()],
 3326            ..LanguageConfig::default()
 3327        },
 3328        None,
 3329    ));
 3330    {
 3331        let mut cx = EditorTestContext::new(cx).await;
 3332        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3333        cx.set_state(indoc! {"
 3334        // Fooˇ
 3335    "});
 3336
 3337        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3338        cx.assert_editor_state(indoc! {"
 3339        // Foo
 3340        // ˇ
 3341    "});
 3342        // Ensure that we add comment prefix when existing line contains space
 3343        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3344        cx.assert_editor_state(
 3345            indoc! {"
 3346        // Foo
 3347        //s
 3348        // ˇ
 3349    "}
 3350            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3351            .as_str(),
 3352        );
 3353        // Ensure that we add comment prefix when existing line does not contain space
 3354        cx.set_state(indoc! {"
 3355        // Foo
 3356        //ˇ
 3357    "});
 3358        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3359        cx.assert_editor_state(indoc! {"
 3360        // Foo
 3361        //
 3362        // ˇ
 3363    "});
 3364        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3365        cx.set_state(indoc! {"
 3366        ˇ// Foo
 3367    "});
 3368        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3369        cx.assert_editor_state(indoc! {"
 3370
 3371        ˇ// Foo
 3372    "});
 3373    }
 3374    // Ensure that comment continuations can be disabled.
 3375    update_test_language_settings(cx, |settings| {
 3376        settings.defaults.extend_comment_on_newline = Some(false);
 3377    });
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.set_state(indoc! {"
 3380        // Fooˇ
 3381    "});
 3382    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383    cx.assert_editor_state(indoc! {"
 3384        // Foo
 3385        ˇ
 3386    "});
 3387}
 3388
 3389#[gpui::test]
 3390async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3391    init_test(cx, |settings| {
 3392        settings.defaults.tab_size = NonZeroU32::new(4)
 3393    });
 3394
 3395    let language = Arc::new(Language::new(
 3396        LanguageConfig {
 3397            line_comments: vec!["// ".into(), "/// ".into()],
 3398            ..LanguageConfig::default()
 3399        },
 3400        None,
 3401    ));
 3402    {
 3403        let mut cx = EditorTestContext::new(cx).await;
 3404        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3405        cx.set_state(indoc! {"
 3406        //ˇ
 3407    "});
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        //
 3411        // ˇ
 3412    "});
 3413
 3414        cx.set_state(indoc! {"
 3415        ///ˇ
 3416    "});
 3417        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418        cx.assert_editor_state(indoc! {"
 3419        ///
 3420        /// ˇ
 3421    "});
 3422    }
 3423}
 3424
 3425#[gpui::test]
 3426async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3427    init_test(cx, |settings| {
 3428        settings.defaults.tab_size = NonZeroU32::new(4)
 3429    });
 3430
 3431    let language = Arc::new(
 3432        Language::new(
 3433            LanguageConfig {
 3434                documentation_comment: Some(language::BlockCommentConfig {
 3435                    start: "/**".into(),
 3436                    end: "*/".into(),
 3437                    prefix: "* ".into(),
 3438                    tab_size: 1,
 3439                }),
 3440
 3441                ..LanguageConfig::default()
 3442            },
 3443            Some(tree_sitter_rust::LANGUAGE.into()),
 3444        )
 3445        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3446        .unwrap(),
 3447    );
 3448
 3449    {
 3450        let mut cx = EditorTestContext::new(cx).await;
 3451        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3452        cx.set_state(indoc! {"
 3453        /**ˇ
 3454    "});
 3455
 3456        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3457        cx.assert_editor_state(indoc! {"
 3458        /**
 3459         * ˇ
 3460    "});
 3461        // Ensure that if cursor is before the comment start,
 3462        // we do not actually insert a comment prefix.
 3463        cx.set_state(indoc! {"
 3464        ˇ/**
 3465    "});
 3466        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3467        cx.assert_editor_state(indoc! {"
 3468
 3469        ˇ/**
 3470    "});
 3471        // Ensure that if cursor is between it doesn't add comment prefix.
 3472        cx.set_state(indoc! {"
 3473        /*ˇ*
 3474    "});
 3475        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3476        cx.assert_editor_state(indoc! {"
 3477        /*
 3478        ˇ*
 3479    "});
 3480        // Ensure that if suffix exists on same line after cursor it adds new line.
 3481        cx.set_state(indoc! {"
 3482        /**ˇ*/
 3483    "});
 3484        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3485        cx.assert_editor_state(indoc! {"
 3486        /**
 3487         * ˇ
 3488         */
 3489    "});
 3490        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3491        cx.set_state(indoc! {"
 3492        /**ˇ */
 3493    "});
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498         */
 3499    "});
 3500        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3501        cx.set_state(indoc! {"
 3502        /** ˇ*/
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(
 3506            indoc! {"
 3507        /**s
 3508         * ˇ
 3509         */
 3510    "}
 3511            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3512            .as_str(),
 3513        );
 3514        // Ensure that delimiter space is preserved when newline on already
 3515        // spaced delimiter.
 3516        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3517        cx.assert_editor_state(
 3518            indoc! {"
 3519        /**s
 3520         *s
 3521         * ˇ
 3522         */
 3523    "}
 3524            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3525            .as_str(),
 3526        );
 3527        // Ensure that delimiter space is preserved when space is not
 3528        // on existing delimiter.
 3529        cx.set_state(indoc! {"
 3530        /**
 3531 3532         */
 3533    "});
 3534        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3535        cx.assert_editor_state(indoc! {"
 3536        /**
 3537         *
 3538         * ˇ
 3539         */
 3540    "});
 3541        // Ensure that if suffix exists on same line after cursor it
 3542        // doesn't add extra new line if prefix is not on same line.
 3543        cx.set_state(indoc! {"
 3544        /**
 3545        ˇ*/
 3546    "});
 3547        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3548        cx.assert_editor_state(indoc! {"
 3549        /**
 3550
 3551        ˇ*/
 3552    "});
 3553        // Ensure that it detects suffix after existing prefix.
 3554        cx.set_state(indoc! {"
 3555        /**ˇ/
 3556    "});
 3557        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3558        cx.assert_editor_state(indoc! {"
 3559        /**
 3560        ˇ/
 3561    "});
 3562        // Ensure that if suffix exists on same line before
 3563        // cursor it does not add comment prefix.
 3564        cx.set_state(indoc! {"
 3565        /** */ˇ
 3566    "});
 3567        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568        cx.assert_editor_state(indoc! {"
 3569        /** */
 3570        ˇ
 3571    "});
 3572        // Ensure that if suffix exists on same line before
 3573        // cursor it does not add comment prefix.
 3574        cx.set_state(indoc! {"
 3575        /**
 3576         *
 3577         */ˇ
 3578    "});
 3579        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3580        cx.assert_editor_state(indoc! {"
 3581        /**
 3582         *
 3583         */
 3584         ˇ
 3585    "});
 3586
 3587        // Ensure that inline comment followed by code
 3588        // doesn't add comment prefix on newline
 3589        cx.set_state(indoc! {"
 3590        /** */ textˇ
 3591    "});
 3592        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3593        cx.assert_editor_state(indoc! {"
 3594        /** */ text
 3595        ˇ
 3596    "});
 3597
 3598        // Ensure that text after comment end tag
 3599        // doesn't add comment prefix on newline
 3600        cx.set_state(indoc! {"
 3601        /**
 3602         *
 3603         */ˇtext
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         *
 3609         */
 3610         ˇtext
 3611    "});
 3612
 3613        // Ensure if not comment block it doesn't
 3614        // add comment prefix on newline
 3615        cx.set_state(indoc! {"
 3616        * textˇ
 3617    "});
 3618        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3619        cx.assert_editor_state(indoc! {"
 3620        * text
 3621        ˇ
 3622    "});
 3623    }
 3624    // Ensure that comment continuations can be disabled.
 3625    update_test_language_settings(cx, |settings| {
 3626        settings.defaults.extend_comment_on_newline = Some(false);
 3627    });
 3628    let mut cx = EditorTestContext::new(cx).await;
 3629    cx.set_state(indoc! {"
 3630        /**ˇ
 3631    "});
 3632    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3633    cx.assert_editor_state(indoc! {"
 3634        /**
 3635        ˇ
 3636    "});
 3637}
 3638
 3639#[gpui::test]
 3640async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3641    init_test(cx, |settings| {
 3642        settings.defaults.tab_size = NonZeroU32::new(4)
 3643    });
 3644
 3645    let lua_language = Arc::new(Language::new(
 3646        LanguageConfig {
 3647            line_comments: vec!["--".into()],
 3648            block_comment: Some(language::BlockCommentConfig {
 3649                start: "--[[".into(),
 3650                prefix: "".into(),
 3651                end: "]]".into(),
 3652                tab_size: 0,
 3653            }),
 3654            ..LanguageConfig::default()
 3655        },
 3656        None,
 3657    ));
 3658
 3659    let mut cx = EditorTestContext::new(cx).await;
 3660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3661
 3662    // Line with line comment should extend
 3663    cx.set_state(indoc! {"
 3664        --ˇ
 3665    "});
 3666    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3667    cx.assert_editor_state(indoc! {"
 3668        --
 3669        --ˇ
 3670    "});
 3671
 3672    // Line with block comment that matches line comment should not extend
 3673    cx.set_state(indoc! {"
 3674        --[[ˇ
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        --[[
 3679        ˇ
 3680    "});
 3681}
 3682
 3683#[gpui::test]
 3684fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3685    init_test(cx, |_| {});
 3686
 3687    let editor = cx.add_window(|window, cx| {
 3688        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3689        let mut editor = build_editor(buffer, window, cx);
 3690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3691            s.select_ranges([3..4, 11..12, 19..20])
 3692        });
 3693        editor
 3694    });
 3695
 3696    _ = editor.update(cx, |editor, window, cx| {
 3697        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3698        editor.buffer.update(cx, |buffer, cx| {
 3699            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3700            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3701        });
 3702        assert_eq!(
 3703            editor.selections.ranges(&editor.display_snapshot(cx)),
 3704            &[2..2, 7..7, 12..12],
 3705        );
 3706
 3707        editor.insert("Z", window, cx);
 3708        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3709
 3710        // The selections are moved after the inserted characters
 3711        assert_eq!(
 3712            editor.selections.ranges(&editor.display_snapshot(cx)),
 3713            &[3..3, 9..9, 15..15],
 3714        );
 3715    });
 3716}
 3717
 3718#[gpui::test]
 3719async fn test_tab(cx: &mut TestAppContext) {
 3720    init_test(cx, |settings| {
 3721        settings.defaults.tab_size = NonZeroU32::new(3)
 3722    });
 3723
 3724    let mut cx = EditorTestContext::new(cx).await;
 3725    cx.set_state(indoc! {"
 3726        ˇabˇc
 3727        ˇ🏀ˇ🏀ˇefg
 3728 3729    "});
 3730    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3731    cx.assert_editor_state(indoc! {"
 3732           ˇab ˇc
 3733           ˇ🏀  ˇ🏀  ˇefg
 3734        d  ˇ
 3735    "});
 3736
 3737    cx.set_state(indoc! {"
 3738        a
 3739        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3740    "});
 3741    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3742    cx.assert_editor_state(indoc! {"
 3743        a
 3744           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3745    "});
 3746}
 3747
 3748#[gpui::test]
 3749async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3750    init_test(cx, |_| {});
 3751
 3752    let mut cx = EditorTestContext::new(cx).await;
 3753    let language = Arc::new(
 3754        Language::new(
 3755            LanguageConfig::default(),
 3756            Some(tree_sitter_rust::LANGUAGE.into()),
 3757        )
 3758        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3759        .unwrap(),
 3760    );
 3761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3762
 3763    // test when all cursors are not at suggested indent
 3764    // then simply move to their suggested indent location
 3765    cx.set_state(indoc! {"
 3766        const a: B = (
 3767            c(
 3768        ˇ
 3769        ˇ    )
 3770        );
 3771    "});
 3772    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3773    cx.assert_editor_state(indoc! {"
 3774        const a: B = (
 3775            c(
 3776                ˇ
 3777            ˇ)
 3778        );
 3779    "});
 3780
 3781    // test cursor already at suggested indent not moving when
 3782    // other cursors are yet to reach their suggested indents
 3783    cx.set_state(indoc! {"
 3784        ˇ
 3785        const a: B = (
 3786            c(
 3787                d(
 3788        ˇ
 3789                )
 3790        ˇ
 3791        ˇ    )
 3792        );
 3793    "});
 3794    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3795    cx.assert_editor_state(indoc! {"
 3796        ˇ
 3797        const a: B = (
 3798            c(
 3799                d(
 3800                    ˇ
 3801                )
 3802                ˇ
 3803            ˇ)
 3804        );
 3805    "});
 3806    // test when all cursors are at suggested indent then tab is inserted
 3807    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3808    cx.assert_editor_state(indoc! {"
 3809            ˇ
 3810        const a: B = (
 3811            c(
 3812                d(
 3813                        ˇ
 3814                )
 3815                    ˇ
 3816                ˇ)
 3817        );
 3818    "});
 3819
 3820    // test when current indent is less than suggested indent,
 3821    // we adjust line to match suggested indent and move cursor to it
 3822    //
 3823    // when no other cursor is at word boundary, all of them should move
 3824    cx.set_state(indoc! {"
 3825        const a: B = (
 3826            c(
 3827                d(
 3828        ˇ
 3829        ˇ   )
 3830        ˇ   )
 3831        );
 3832    "});
 3833    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3834    cx.assert_editor_state(indoc! {"
 3835        const a: B = (
 3836            c(
 3837                d(
 3838                    ˇ
 3839                ˇ)
 3840            ˇ)
 3841        );
 3842    "});
 3843
 3844    // test when current indent is less than suggested indent,
 3845    // we adjust line to match suggested indent and move cursor to it
 3846    //
 3847    // when some other cursor is at word boundary, it should not move
 3848    cx.set_state(indoc! {"
 3849        const a: B = (
 3850            c(
 3851                d(
 3852        ˇ
 3853        ˇ   )
 3854           ˇ)
 3855        );
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        const a: B = (
 3860            c(
 3861                d(
 3862                    ˇ
 3863                ˇ)
 3864            ˇ)
 3865        );
 3866    "});
 3867
 3868    // test when current indent is more than suggested indent,
 3869    // we just move cursor to current indent instead of suggested indent
 3870    //
 3871    // when no other cursor is at word boundary, all of them should move
 3872    cx.set_state(indoc! {"
 3873        const a: B = (
 3874            c(
 3875                d(
 3876        ˇ
 3877        ˇ                )
 3878        ˇ   )
 3879        );
 3880    "});
 3881    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3882    cx.assert_editor_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886                    ˇ
 3887                        ˇ)
 3888            ˇ)
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                        ˇ
 3897                            ˇ)
 3898                ˇ)
 3899        );
 3900    "});
 3901
 3902    // test when current indent is more than suggested indent,
 3903    // we just move cursor to current indent instead of suggested indent
 3904    //
 3905    // when some other cursor is at word boundary, it doesn't move
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909                d(
 3910        ˇ
 3911        ˇ                )
 3912            ˇ)
 3913        );
 3914    "});
 3915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3916    cx.assert_editor_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920                    ˇ
 3921                        ˇ)
 3922            ˇ)
 3923        );
 3924    "});
 3925
 3926    // handle auto-indent when there are multiple cursors on the same line
 3927    cx.set_state(indoc! {"
 3928        const a: B = (
 3929            c(
 3930        ˇ    ˇ
 3931        ˇ    )
 3932        );
 3933    "});
 3934    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3935    cx.assert_editor_state(indoc! {"
 3936        const a: B = (
 3937            c(
 3938                ˇ
 3939            ˇ)
 3940        );
 3941    "});
 3942}
 3943
 3944#[gpui::test]
 3945async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3946    init_test(cx, |settings| {
 3947        settings.defaults.tab_size = NonZeroU32::new(3)
 3948    });
 3949
 3950    let mut cx = EditorTestContext::new(cx).await;
 3951    cx.set_state(indoc! {"
 3952         ˇ
 3953        \t ˇ
 3954        \t  ˇ
 3955        \t   ˇ
 3956         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3957    "});
 3958
 3959    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3960    cx.assert_editor_state(indoc! {"
 3961           ˇ
 3962        \t   ˇ
 3963        \t   ˇ
 3964        \t      ˇ
 3965         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3966    "});
 3967}
 3968
 3969#[gpui::test]
 3970async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3971    init_test(cx, |settings| {
 3972        settings.defaults.tab_size = NonZeroU32::new(4)
 3973    });
 3974
 3975    let language = Arc::new(
 3976        Language::new(
 3977            LanguageConfig::default(),
 3978            Some(tree_sitter_rust::LANGUAGE.into()),
 3979        )
 3980        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3981        .unwrap(),
 3982    );
 3983
 3984    let mut cx = EditorTestContext::new(cx).await;
 3985    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3986    cx.set_state(indoc! {"
 3987        fn a() {
 3988            if b {
 3989        \t ˇc
 3990            }
 3991        }
 3992    "});
 3993
 3994    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3995    cx.assert_editor_state(indoc! {"
 3996        fn a() {
 3997            if b {
 3998                ˇc
 3999            }
 4000        }
 4001    "});
 4002}
 4003
 4004#[gpui::test]
 4005async fn test_indent_outdent(cx: &mut TestAppContext) {
 4006    init_test(cx, |settings| {
 4007        settings.defaults.tab_size = NonZeroU32::new(4);
 4008    });
 4009
 4010    let mut cx = EditorTestContext::new(cx).await;
 4011
 4012    cx.set_state(indoc! {"
 4013          «oneˇ» «twoˇ»
 4014        three
 4015         four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019            «oneˇ» «twoˇ»
 4020        three
 4021         four
 4022    "});
 4023
 4024    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4025    cx.assert_editor_state(indoc! {"
 4026        «oneˇ» «twoˇ»
 4027        three
 4028         four
 4029    "});
 4030
 4031    // select across line ending
 4032    cx.set_state(indoc! {"
 4033        one two
 4034        t«hree
 4035        ˇ» four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        one two
 4040            t«hree
 4041        ˇ» four
 4042    "});
 4043
 4044    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4045    cx.assert_editor_state(indoc! {"
 4046        one two
 4047        t«hree
 4048        ˇ» four
 4049    "});
 4050
 4051    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4052    cx.set_state(indoc! {"
 4053        one two
 4054        ˇthree
 4055            four
 4056    "});
 4057    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4058    cx.assert_editor_state(indoc! {"
 4059        one two
 4060            ˇthree
 4061            four
 4062    "});
 4063
 4064    cx.set_state(indoc! {"
 4065        one two
 4066        ˇ    three
 4067            four
 4068    "});
 4069    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4070    cx.assert_editor_state(indoc! {"
 4071        one two
 4072        ˇthree
 4073            four
 4074    "});
 4075}
 4076
 4077#[gpui::test]
 4078async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4079    // This is a regression test for issue #33761
 4080    init_test(cx, |_| {});
 4081
 4082    let mut cx = EditorTestContext::new(cx).await;
 4083    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4085
 4086    cx.set_state(
 4087        r#"ˇ#     ingress:
 4088ˇ#         api:
 4089ˇ#             enabled: false
 4090ˇ#             pathType: Prefix
 4091ˇ#           console:
 4092ˇ#               enabled: false
 4093ˇ#               pathType: Prefix
 4094"#,
 4095    );
 4096
 4097    // Press tab to indent all lines
 4098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4099
 4100    cx.assert_editor_state(
 4101        r#"    ˇ#     ingress:
 4102    ˇ#         api:
 4103    ˇ#             enabled: false
 4104    ˇ#             pathType: Prefix
 4105    ˇ#           console:
 4106    ˇ#               enabled: false
 4107    ˇ#               pathType: Prefix
 4108"#,
 4109    );
 4110}
 4111
 4112#[gpui::test]
 4113async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4114    // This is a test to make sure our fix for issue #33761 didn't break anything
 4115    init_test(cx, |_| {});
 4116
 4117    let mut cx = EditorTestContext::new(cx).await;
 4118    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4119    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4120
 4121    cx.set_state(
 4122        r#"ˇingress:
 4123ˇ  api:
 4124ˇ    enabled: false
 4125ˇ    pathType: Prefix
 4126"#,
 4127    );
 4128
 4129    // Press tab to indent all lines
 4130    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4131
 4132    cx.assert_editor_state(
 4133        r#"ˇingress:
 4134    ˇapi:
 4135        ˇenabled: false
 4136        ˇpathType: Prefix
 4137"#,
 4138    );
 4139}
 4140
 4141#[gpui::test]
 4142async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4143    init_test(cx, |settings| {
 4144        settings.defaults.hard_tabs = Some(true);
 4145    });
 4146
 4147    let mut cx = EditorTestContext::new(cx).await;
 4148
 4149    // select two ranges on one line
 4150    cx.set_state(indoc! {"
 4151        «oneˇ» «twoˇ»
 4152        three
 4153        four
 4154    "});
 4155    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4156    cx.assert_editor_state(indoc! {"
 4157        \t«oneˇ» «twoˇ»
 4158        three
 4159        four
 4160    "});
 4161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4162    cx.assert_editor_state(indoc! {"
 4163        \t\t«oneˇ» «twoˇ»
 4164        three
 4165        four
 4166    "});
 4167    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4168    cx.assert_editor_state(indoc! {"
 4169        \t«oneˇ» «twoˇ»
 4170        three
 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        three
 4177        four
 4178    "});
 4179
 4180    // select across a line ending
 4181    cx.set_state(indoc! {"
 4182        one two
 4183        t«hree
 4184        ˇ»four
 4185    "});
 4186    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4187    cx.assert_editor_state(indoc! {"
 4188        one two
 4189        \tt«hree
 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\tt«hree
 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        \tt«hree
 4202        ˇ»four
 4203    "});
 4204    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4205    cx.assert_editor_state(indoc! {"
 4206        one two
 4207        t«hree
 4208        ˇ»four
 4209    "});
 4210
 4211    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4212    cx.set_state(indoc! {"
 4213        one two
 4214        ˇthree
 4215        four
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        one two
 4220        ˇthree
 4221        four
 4222    "});
 4223    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4224    cx.assert_editor_state(indoc! {"
 4225        one two
 4226        \tˇthree
 4227        four
 4228    "});
 4229    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4230    cx.assert_editor_state(indoc! {"
 4231        one two
 4232        ˇthree
 4233        four
 4234    "});
 4235}
 4236
 4237#[gpui::test]
 4238fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4239    init_test(cx, |settings| {
 4240        settings.languages.0.extend([
 4241            (
 4242                "TOML".into(),
 4243                LanguageSettingsContent {
 4244                    tab_size: NonZeroU32::new(2),
 4245                    ..Default::default()
 4246                },
 4247            ),
 4248            (
 4249                "Rust".into(),
 4250                LanguageSettingsContent {
 4251                    tab_size: NonZeroU32::new(4),
 4252                    ..Default::default()
 4253                },
 4254            ),
 4255        ]);
 4256    });
 4257
 4258    let toml_language = Arc::new(Language::new(
 4259        LanguageConfig {
 4260            name: "TOML".into(),
 4261            ..Default::default()
 4262        },
 4263        None,
 4264    ));
 4265    let rust_language = Arc::new(Language::new(
 4266        LanguageConfig {
 4267            name: "Rust".into(),
 4268            ..Default::default()
 4269        },
 4270        None,
 4271    ));
 4272
 4273    let toml_buffer =
 4274        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4275    let rust_buffer =
 4276        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4277    let multibuffer = cx.new(|cx| {
 4278        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4279        multibuffer.push_excerpts(
 4280            toml_buffer.clone(),
 4281            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4282            cx,
 4283        );
 4284        multibuffer.push_excerpts(
 4285            rust_buffer.clone(),
 4286            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4287            cx,
 4288        );
 4289        multibuffer
 4290    });
 4291
 4292    cx.add_window(|window, cx| {
 4293        let mut editor = build_editor(multibuffer, window, cx);
 4294
 4295        assert_eq!(
 4296            editor.text(cx),
 4297            indoc! {"
 4298                a = 1
 4299                b = 2
 4300
 4301                const c: usize = 3;
 4302            "}
 4303        );
 4304
 4305        select_ranges(
 4306            &mut editor,
 4307            indoc! {"
 4308                «aˇ» = 1
 4309                b = 2
 4310
 4311                «const c:ˇ» usize = 3;
 4312            "},
 4313            window,
 4314            cx,
 4315        );
 4316
 4317        editor.tab(&Tab, window, cx);
 4318        assert_text_with_selections(
 4319            &mut editor,
 4320            indoc! {"
 4321                  «aˇ» = 1
 4322                b = 2
 4323
 4324                    «const c:ˇ» usize = 3;
 4325            "},
 4326            cx,
 4327        );
 4328        editor.backtab(&Backtab, window, cx);
 4329        assert_text_with_selections(
 4330            &mut editor,
 4331            indoc! {"
 4332                «aˇ» = 1
 4333                b = 2
 4334
 4335                «const c:ˇ» usize = 3;
 4336            "},
 4337            cx,
 4338        );
 4339
 4340        editor
 4341    });
 4342}
 4343
 4344#[gpui::test]
 4345async fn test_backspace(cx: &mut TestAppContext) {
 4346    init_test(cx, |_| {});
 4347
 4348    let mut cx = EditorTestContext::new(cx).await;
 4349
 4350    // Basic backspace
 4351    cx.set_state(indoc! {"
 4352        onˇe two three
 4353        fou«rˇ» five six
 4354        seven «ˇeight nine
 4355        »ten
 4356    "});
 4357    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4358    cx.assert_editor_state(indoc! {"
 4359        oˇe two three
 4360        fouˇ five six
 4361        seven ˇten
 4362    "});
 4363
 4364    // Test backspace inside and around indents
 4365    cx.set_state(indoc! {"
 4366        zero
 4367            ˇone
 4368                ˇtwo
 4369            ˇ ˇ ˇ  three
 4370        ˇ  ˇ  four
 4371    "});
 4372    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4373    cx.assert_editor_state(indoc! {"
 4374        zero
 4375        ˇone
 4376            ˇtwo
 4377        ˇ  threeˇ  four
 4378    "});
 4379}
 4380
 4381#[gpui::test]
 4382async fn test_delete(cx: &mut TestAppContext) {
 4383    init_test(cx, |_| {});
 4384
 4385    let mut cx = EditorTestContext::new(cx).await;
 4386    cx.set_state(indoc! {"
 4387        onˇe two three
 4388        fou«rˇ» five six
 4389        seven «ˇeight nine
 4390        »ten
 4391    "});
 4392    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4393    cx.assert_editor_state(indoc! {"
 4394        onˇ two three
 4395        fouˇ five six
 4396        seven ˇten
 4397    "});
 4398}
 4399
 4400#[gpui::test]
 4401fn test_delete_line(cx: &mut TestAppContext) {
 4402    init_test(cx, |_| {});
 4403
 4404    let editor = cx.add_window(|window, cx| {
 4405        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4406        build_editor(buffer, window, cx)
 4407    });
 4408    _ = editor.update(cx, |editor, window, cx| {
 4409        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4410            s.select_display_ranges([
 4411                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4412                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4413                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4414            ])
 4415        });
 4416        editor.delete_line(&DeleteLine, window, cx);
 4417        assert_eq!(editor.display_text(cx), "ghi");
 4418        assert_eq!(
 4419            editor.selections.display_ranges(cx),
 4420            vec![
 4421                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4422                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4423            ]
 4424        );
 4425    });
 4426
 4427    let editor = cx.add_window(|window, cx| {
 4428        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4429        build_editor(buffer, window, cx)
 4430    });
 4431    _ = editor.update(cx, |editor, window, cx| {
 4432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4433            s.select_display_ranges([
 4434                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4435            ])
 4436        });
 4437        editor.delete_line(&DeleteLine, window, cx);
 4438        assert_eq!(editor.display_text(cx), "ghi\n");
 4439        assert_eq!(
 4440            editor.selections.display_ranges(cx),
 4441            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4442        );
 4443    });
 4444
 4445    let editor = cx.add_window(|window, cx| {
 4446        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4447        build_editor(buffer, window, cx)
 4448    });
 4449    _ = editor.update(cx, |editor, window, cx| {
 4450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4451            s.select_display_ranges([
 4452                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4453            ])
 4454        });
 4455        editor.delete_line(&DeleteLine, window, cx);
 4456        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4457        assert_eq!(
 4458            editor.selections.display_ranges(cx),
 4459            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4460        );
 4461    });
 4462}
 4463
 4464#[gpui::test]
 4465fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4466    init_test(cx, |_| {});
 4467
 4468    cx.add_window(|window, cx| {
 4469        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4470        let mut editor = build_editor(buffer.clone(), window, cx);
 4471        let buffer = buffer.read(cx).as_singleton().unwrap();
 4472
 4473        assert_eq!(
 4474            editor
 4475                .selections
 4476                .ranges::<Point>(&editor.display_snapshot(cx)),
 4477            &[Point::new(0, 0)..Point::new(0, 0)]
 4478        );
 4479
 4480        // When on single line, replace newline at end by space
 4481        editor.join_lines(&JoinLines, window, cx);
 4482        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4483        assert_eq!(
 4484            editor
 4485                .selections
 4486                .ranges::<Point>(&editor.display_snapshot(cx)),
 4487            &[Point::new(0, 3)..Point::new(0, 3)]
 4488        );
 4489
 4490        // When multiple lines are selected, remove newlines that are spanned by the selection
 4491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4492            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4493        });
 4494        editor.join_lines(&JoinLines, window, cx);
 4495        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4496        assert_eq!(
 4497            editor
 4498                .selections
 4499                .ranges::<Point>(&editor.display_snapshot(cx)),
 4500            &[Point::new(0, 11)..Point::new(0, 11)]
 4501        );
 4502
 4503        // Undo should be transactional
 4504        editor.undo(&Undo, window, cx);
 4505        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4506        assert_eq!(
 4507            editor
 4508                .selections
 4509                .ranges::<Point>(&editor.display_snapshot(cx)),
 4510            &[Point::new(0, 5)..Point::new(2, 2)]
 4511        );
 4512
 4513        // When joining an empty line don't insert a space
 4514        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4515            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4516        });
 4517        editor.join_lines(&JoinLines, window, cx);
 4518        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4519        assert_eq!(
 4520            editor
 4521                .selections
 4522                .ranges::<Point>(&editor.display_snapshot(cx)),
 4523            [Point::new(2, 3)..Point::new(2, 3)]
 4524        );
 4525
 4526        // We can remove trailing newlines
 4527        editor.join_lines(&JoinLines, window, cx);
 4528        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            [Point::new(2, 3)..Point::new(2, 3)]
 4534        );
 4535
 4536        // We don't blow up on the last line
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            [Point::new(2, 3)..Point::new(2, 3)]
 4544        );
 4545
 4546        // reset to test indentation
 4547        editor.buffer.update(cx, |buffer, cx| {
 4548            buffer.edit(
 4549                [
 4550                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4551                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4552                ],
 4553                None,
 4554                cx,
 4555            )
 4556        });
 4557
 4558        // We remove any leading spaces
 4559        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4560        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4561            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4562        });
 4563        editor.join_lines(&JoinLines, window, cx);
 4564        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4565
 4566        // We don't insert a space for a line containing only spaces
 4567        editor.join_lines(&JoinLines, window, cx);
 4568        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4569
 4570        // We ignore any leading tabs
 4571        editor.join_lines(&JoinLines, window, cx);
 4572        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4573
 4574        editor
 4575    });
 4576}
 4577
 4578#[gpui::test]
 4579fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4580    init_test(cx, |_| {});
 4581
 4582    cx.add_window(|window, cx| {
 4583        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4584        let mut editor = build_editor(buffer.clone(), window, cx);
 4585        let buffer = buffer.read(cx).as_singleton().unwrap();
 4586
 4587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4588            s.select_ranges([
 4589                Point::new(0, 2)..Point::new(1, 1),
 4590                Point::new(1, 2)..Point::new(1, 2),
 4591                Point::new(3, 1)..Point::new(3, 2),
 4592            ])
 4593        });
 4594
 4595        editor.join_lines(&JoinLines, window, cx);
 4596        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4597
 4598        assert_eq!(
 4599            editor
 4600                .selections
 4601                .ranges::<Point>(&editor.display_snapshot(cx)),
 4602            [
 4603                Point::new(0, 7)..Point::new(0, 7),
 4604                Point::new(1, 3)..Point::new(1, 3)
 4605            ]
 4606        );
 4607        editor
 4608    });
 4609}
 4610
 4611#[gpui::test]
 4612async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4613    init_test(cx, |_| {});
 4614
 4615    let mut cx = EditorTestContext::new(cx).await;
 4616
 4617    let diff_base = r#"
 4618        Line 0
 4619        Line 1
 4620        Line 2
 4621        Line 3
 4622        "#
 4623    .unindent();
 4624
 4625    cx.set_state(
 4626        &r#"
 4627        ˇLine 0
 4628        Line 1
 4629        Line 2
 4630        Line 3
 4631        "#
 4632        .unindent(),
 4633    );
 4634
 4635    cx.set_head_text(&diff_base);
 4636    executor.run_until_parked();
 4637
 4638    // Join lines
 4639    cx.update_editor(|editor, window, cx| {
 4640        editor.join_lines(&JoinLines, window, cx);
 4641    });
 4642    executor.run_until_parked();
 4643
 4644    cx.assert_editor_state(
 4645        &r#"
 4646        Line 0ˇ Line 1
 4647        Line 2
 4648        Line 3
 4649        "#
 4650        .unindent(),
 4651    );
 4652    // Join again
 4653    cx.update_editor(|editor, window, cx| {
 4654        editor.join_lines(&JoinLines, window, cx);
 4655    });
 4656    executor.run_until_parked();
 4657
 4658    cx.assert_editor_state(
 4659        &r#"
 4660        Line 0 Line 1ˇ Line 2
 4661        Line 3
 4662        "#
 4663        .unindent(),
 4664    );
 4665}
 4666
 4667#[gpui::test]
 4668async fn test_custom_newlines_cause_no_false_positive_diffs(
 4669    executor: BackgroundExecutor,
 4670    cx: &mut TestAppContext,
 4671) {
 4672    init_test(cx, |_| {});
 4673    let mut cx = EditorTestContext::new(cx).await;
 4674    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4675    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4676    executor.run_until_parked();
 4677
 4678    cx.update_editor(|editor, window, cx| {
 4679        let snapshot = editor.snapshot(window, cx);
 4680        assert_eq!(
 4681            snapshot
 4682                .buffer_snapshot()
 4683                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4684                .collect::<Vec<_>>(),
 4685            Vec::new(),
 4686            "Should not have any diffs for files with custom newlines"
 4687        );
 4688    });
 4689}
 4690
 4691#[gpui::test]
 4692async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4693    init_test(cx, |_| {});
 4694
 4695    let mut cx = EditorTestContext::new(cx).await;
 4696
 4697    // Test sort_lines_case_insensitive()
 4698    cx.set_state(indoc! {"
 4699        «z
 4700        y
 4701        x
 4702        Z
 4703        Y
 4704        Xˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| {
 4707        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4708    });
 4709    cx.assert_editor_state(indoc! {"
 4710        «x
 4711        X
 4712        y
 4713        Y
 4714        z
 4715        Zˇ»
 4716    "});
 4717
 4718    // Test sort_lines_by_length()
 4719    //
 4720    // Demonstrates:
 4721    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4722    // - sort is stable
 4723    cx.set_state(indoc! {"
 4724        «123
 4725        æ
 4726        12
 4727 4728        1
 4729        æˇ»
 4730    "});
 4731    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4732    cx.assert_editor_state(indoc! {"
 4733        «æ
 4734 4735        1
 4736        æ
 4737        12
 4738        123ˇ»
 4739    "});
 4740
 4741    // Test reverse_lines()
 4742    cx.set_state(indoc! {"
 4743        «5
 4744        4
 4745        3
 4746        2
 4747        1ˇ»
 4748    "});
 4749    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4750    cx.assert_editor_state(indoc! {"
 4751        «1
 4752        2
 4753        3
 4754        4
 4755        5ˇ»
 4756    "});
 4757
 4758    // Skip testing shuffle_line()
 4759
 4760    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4761    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4762
 4763    // Don't manipulate when cursor is on single line, but expand the selection
 4764    cx.set_state(indoc! {"
 4765        ddˇdd
 4766        ccc
 4767        bb
 4768        a
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «ddddˇ»
 4775        ccc
 4776        bb
 4777        a
 4778    "});
 4779
 4780    // Basic manipulate case
 4781    // Start selection moves to column 0
 4782    // End of selection shrinks to fit shorter line
 4783    cx.set_state(indoc! {"
 4784        dd«d
 4785        ccc
 4786        bb
 4787        aaaaaˇ»
 4788    "});
 4789    cx.update_editor(|e, window, cx| {
 4790        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4791    });
 4792    cx.assert_editor_state(indoc! {"
 4793        «aaaaa
 4794        bb
 4795        ccc
 4796        dddˇ»
 4797    "});
 4798
 4799    // Manipulate case with newlines
 4800    cx.set_state(indoc! {"
 4801        dd«d
 4802        ccc
 4803
 4804        bb
 4805        aaaaa
 4806
 4807        ˇ»
 4808    "});
 4809    cx.update_editor(|e, window, cx| {
 4810        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4811    });
 4812    cx.assert_editor_state(indoc! {"
 4813        «
 4814
 4815        aaaaa
 4816        bb
 4817        ccc
 4818        dddˇ»
 4819
 4820    "});
 4821
 4822    // Adding new line
 4823    cx.set_state(indoc! {"
 4824        aa«a
 4825        bbˇ»b
 4826    "});
 4827    cx.update_editor(|e, window, cx| {
 4828        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4829    });
 4830    cx.assert_editor_state(indoc! {"
 4831        «aaa
 4832        bbb
 4833        added_lineˇ»
 4834    "});
 4835
 4836    // Removing line
 4837    cx.set_state(indoc! {"
 4838        aa«a
 4839        bbbˇ»
 4840    "});
 4841    cx.update_editor(|e, window, cx| {
 4842        e.manipulate_immutable_lines(window, cx, |lines| {
 4843            lines.pop();
 4844        })
 4845    });
 4846    cx.assert_editor_state(indoc! {"
 4847        «aaaˇ»
 4848    "});
 4849
 4850    // Removing all lines
 4851    cx.set_state(indoc! {"
 4852        aa«a
 4853        bbbˇ»
 4854    "});
 4855    cx.update_editor(|e, window, cx| {
 4856        e.manipulate_immutable_lines(window, cx, |lines| {
 4857            lines.drain(..);
 4858        })
 4859    });
 4860    cx.assert_editor_state(indoc! {"
 4861        ˇ
 4862    "});
 4863}
 4864
 4865#[gpui::test]
 4866async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4867    init_test(cx, |_| {});
 4868
 4869    let mut cx = EditorTestContext::new(cx).await;
 4870
 4871    // Consider continuous selection as single selection
 4872    cx.set_state(indoc! {"
 4873        Aaa«aa
 4874        cˇ»c«c
 4875        bb
 4876        aaaˇ»aa
 4877    "});
 4878    cx.update_editor(|e, window, cx| {
 4879        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4880    });
 4881    cx.assert_editor_state(indoc! {"
 4882        «Aaaaa
 4883        ccc
 4884        bb
 4885        aaaaaˇ»
 4886    "});
 4887
 4888    cx.set_state(indoc! {"
 4889        Aaa«aa
 4890        cˇ»c«c
 4891        bb
 4892        aaaˇ»aa
 4893    "});
 4894    cx.update_editor(|e, window, cx| {
 4895        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4896    });
 4897    cx.assert_editor_state(indoc! {"
 4898        «Aaaaa
 4899        ccc
 4900        bbˇ»
 4901    "});
 4902
 4903    // Consider non continuous selection as distinct dedup operations
 4904    cx.set_state(indoc! {"
 4905        «aaaaa
 4906        bb
 4907        aaaaa
 4908        aaaaaˇ»
 4909
 4910        aaa«aaˇ»
 4911    "});
 4912    cx.update_editor(|e, window, cx| {
 4913        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4914    });
 4915    cx.assert_editor_state(indoc! {"
 4916        «aaaaa
 4917        bbˇ»
 4918
 4919        «aaaaaˇ»
 4920    "});
 4921}
 4922
 4923#[gpui::test]
 4924async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4925    init_test(cx, |_| {});
 4926
 4927    let mut cx = EditorTestContext::new(cx).await;
 4928
 4929    cx.set_state(indoc! {"
 4930        «Aaa
 4931        aAa
 4932        Aaaˇ»
 4933    "});
 4934    cx.update_editor(|e, window, cx| {
 4935        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4936    });
 4937    cx.assert_editor_state(indoc! {"
 4938        «Aaa
 4939        aAaˇ»
 4940    "});
 4941
 4942    cx.set_state(indoc! {"
 4943        «Aaa
 4944        aAa
 4945        aaAˇ»
 4946    "});
 4947    cx.update_editor(|e, window, cx| {
 4948        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4949    });
 4950    cx.assert_editor_state(indoc! {"
 4951        «Aaaˇ»
 4952    "});
 4953}
 4954
 4955#[gpui::test]
 4956async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4957    init_test(cx, |_| {});
 4958
 4959    let mut cx = EditorTestContext::new(cx).await;
 4960
 4961    let js_language = Arc::new(Language::new(
 4962        LanguageConfig {
 4963            name: "JavaScript".into(),
 4964            wrap_characters: Some(language::WrapCharactersConfig {
 4965                start_prefix: "<".into(),
 4966                start_suffix: ">".into(),
 4967                end_prefix: "</".into(),
 4968                end_suffix: ">".into(),
 4969            }),
 4970            ..LanguageConfig::default()
 4971        },
 4972        None,
 4973    ));
 4974
 4975    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4976
 4977    cx.set_state(indoc! {"
 4978        «testˇ»
 4979    "});
 4980    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4981    cx.assert_editor_state(indoc! {"
 4982        <«ˇ»>test</«ˇ»>
 4983    "});
 4984
 4985    cx.set_state(indoc! {"
 4986        «test
 4987         testˇ»
 4988    "});
 4989    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4990    cx.assert_editor_state(indoc! {"
 4991        <«ˇ»>test
 4992         test</«ˇ»>
 4993    "});
 4994
 4995    cx.set_state(indoc! {"
 4996        teˇst
 4997    "});
 4998    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4999    cx.assert_editor_state(indoc! {"
 5000        te<«ˇ»></«ˇ»>st
 5001    "});
 5002}
 5003
 5004#[gpui::test]
 5005async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5006    init_test(cx, |_| {});
 5007
 5008    let mut cx = EditorTestContext::new(cx).await;
 5009
 5010    let js_language = Arc::new(Language::new(
 5011        LanguageConfig {
 5012            name: "JavaScript".into(),
 5013            wrap_characters: Some(language::WrapCharactersConfig {
 5014                start_prefix: "<".into(),
 5015                start_suffix: ">".into(),
 5016                end_prefix: "</".into(),
 5017                end_suffix: ">".into(),
 5018            }),
 5019            ..LanguageConfig::default()
 5020        },
 5021        None,
 5022    ));
 5023
 5024    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5025
 5026    cx.set_state(indoc! {"
 5027        «testˇ»
 5028        «testˇ» «testˇ»
 5029        «testˇ»
 5030    "});
 5031    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5032    cx.assert_editor_state(indoc! {"
 5033        <«ˇ»>test</«ˇ»>
 5034        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5035        <«ˇ»>test</«ˇ»>
 5036    "});
 5037
 5038    cx.set_state(indoc! {"
 5039        «test
 5040         testˇ»
 5041        «test
 5042         testˇ»
 5043    "});
 5044    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5045    cx.assert_editor_state(indoc! {"
 5046        <«ˇ»>test
 5047         test</«ˇ»>
 5048        <«ˇ»>test
 5049         test</«ˇ»>
 5050    "});
 5051}
 5052
 5053#[gpui::test]
 5054async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5055    init_test(cx, |_| {});
 5056
 5057    let mut cx = EditorTestContext::new(cx).await;
 5058
 5059    let plaintext_language = Arc::new(Language::new(
 5060        LanguageConfig {
 5061            name: "Plain Text".into(),
 5062            ..LanguageConfig::default()
 5063        },
 5064        None,
 5065    ));
 5066
 5067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5068
 5069    cx.set_state(indoc! {"
 5070        «testˇ»
 5071    "});
 5072    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5073    cx.assert_editor_state(indoc! {"
 5074      «testˇ»
 5075    "});
 5076}
 5077
 5078#[gpui::test]
 5079async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5080    init_test(cx, |_| {});
 5081
 5082    let mut cx = EditorTestContext::new(cx).await;
 5083
 5084    // Manipulate with multiple selections on a single line
 5085    cx.set_state(indoc! {"
 5086        dd«dd
 5087        cˇ»c«c
 5088        bb
 5089        aaaˇ»aa
 5090    "});
 5091    cx.update_editor(|e, window, cx| {
 5092        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5093    });
 5094    cx.assert_editor_state(indoc! {"
 5095        «aaaaa
 5096        bb
 5097        ccc
 5098        ddddˇ»
 5099    "});
 5100
 5101    // Manipulate with multiple disjoin selections
 5102    cx.set_state(indoc! {"
 5103 5104        4
 5105        3
 5106        2
 5107        1ˇ»
 5108
 5109        dd«dd
 5110        ccc
 5111        bb
 5112        aaaˇ»aa
 5113    "});
 5114    cx.update_editor(|e, window, cx| {
 5115        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5116    });
 5117    cx.assert_editor_state(indoc! {"
 5118        «1
 5119        2
 5120        3
 5121        4
 5122        5ˇ»
 5123
 5124        «aaaaa
 5125        bb
 5126        ccc
 5127        ddddˇ»
 5128    "});
 5129
 5130    // Adding lines on each selection
 5131    cx.set_state(indoc! {"
 5132 5133        1ˇ»
 5134
 5135        bb«bb
 5136        aaaˇ»aa
 5137    "});
 5138    cx.update_editor(|e, window, cx| {
 5139        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5140    });
 5141    cx.assert_editor_state(indoc! {"
 5142        «2
 5143        1
 5144        added lineˇ»
 5145
 5146        «bbbb
 5147        aaaaa
 5148        added lineˇ»
 5149    "});
 5150
 5151    // Removing lines on each selection
 5152    cx.set_state(indoc! {"
 5153 5154        1ˇ»
 5155
 5156        bb«bb
 5157        aaaˇ»aa
 5158    "});
 5159    cx.update_editor(|e, window, cx| {
 5160        e.manipulate_immutable_lines(window, cx, |lines| {
 5161            lines.pop();
 5162        })
 5163    });
 5164    cx.assert_editor_state(indoc! {"
 5165        «2ˇ»
 5166
 5167        «bbbbˇ»
 5168    "});
 5169}
 5170
 5171#[gpui::test]
 5172async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5173    init_test(cx, |settings| {
 5174        settings.defaults.tab_size = NonZeroU32::new(3)
 5175    });
 5176
 5177    let mut cx = EditorTestContext::new(cx).await;
 5178
 5179    // MULTI SELECTION
 5180    // Ln.1 "«" tests empty lines
 5181    // Ln.9 tests just leading whitespace
 5182    cx.set_state(indoc! {"
 5183        «
 5184        abc                 // No indentationˇ»
 5185        «\tabc              // 1 tabˇ»
 5186        \t\tabc «      ˇ»   // 2 tabs
 5187        \t ab«c             // Tab followed by space
 5188         \tabc              // Space followed by tab (3 spaces should be the result)
 5189        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5190           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5191        \t
 5192        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5193    "});
 5194    cx.update_editor(|e, window, cx| {
 5195        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5196    });
 5197    cx.assert_editor_state(
 5198        indoc! {"
 5199            «
 5200            abc                 // No indentation
 5201               abc              // 1 tab
 5202                  abc          // 2 tabs
 5203                abc             // Tab followed by space
 5204               abc              // Space followed by tab (3 spaces should be the result)
 5205                           abc   // Mixed indentation (tab conversion depends on the column)
 5206               abc         // Already space indented
 5207               ·
 5208               abc\tdef          // Only the leading tab is manipulatedˇ»
 5209        "}
 5210        .replace("·", "")
 5211        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5212    );
 5213
 5214    // Test on just a few lines, the others should remain unchanged
 5215    // Only lines (3, 5, 10, 11) should change
 5216    cx.set_state(
 5217        indoc! {"
 5218            ·
 5219            abc                 // No indentation
 5220            \tabcˇ               // 1 tab
 5221            \t\tabc             // 2 tabs
 5222            \t abcˇ              // Tab followed by space
 5223             \tabc              // Space followed by tab (3 spaces should be the result)
 5224            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5225               abc              // Already space indented
 5226            «\t
 5227            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5228        "}
 5229        .replace("·", "")
 5230        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5231    );
 5232    cx.update_editor(|e, window, cx| {
 5233        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5234    });
 5235    cx.assert_editor_state(
 5236        indoc! {"
 5237            ·
 5238            abc                 // No indentation
 5239            «   abc               // 1 tabˇ»
 5240            \t\tabc             // 2 tabs
 5241            «    abc              // Tab followed by spaceˇ»
 5242             \tabc              // Space followed by tab (3 spaces should be the result)
 5243            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5244               abc              // Already space indented
 5245            «   ·
 5246               abc\tdef          // Only the leading tab is manipulatedˇ»
 5247        "}
 5248        .replace("·", "")
 5249        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5250    );
 5251
 5252    // SINGLE SELECTION
 5253    // Ln.1 "«" tests empty lines
 5254    // Ln.9 tests just leading whitespace
 5255    cx.set_state(indoc! {"
 5256        «
 5257        abc                 // No indentation
 5258        \tabc               // 1 tab
 5259        \t\tabc             // 2 tabs
 5260        \t abc              // Tab followed by space
 5261         \tabc              // Space followed by tab (3 spaces should be the result)
 5262        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5263           abc              // Already space indented
 5264        \t
 5265        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5266    "});
 5267    cx.update_editor(|e, window, cx| {
 5268        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5269    });
 5270    cx.assert_editor_state(
 5271        indoc! {"
 5272            «
 5273            abc                 // No indentation
 5274               abc               // 1 tab
 5275                  abc             // 2 tabs
 5276                abc              // Tab followed by space
 5277               abc              // Space followed by tab (3 spaces should be the result)
 5278                           abc   // Mixed indentation (tab conversion depends on the column)
 5279               abc              // Already space indented
 5280               ·
 5281               abc\tdef          // Only the leading tab is manipulatedˇ»
 5282        "}
 5283        .replace("·", "")
 5284        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5285    );
 5286}
 5287
 5288#[gpui::test]
 5289async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5290    init_test(cx, |settings| {
 5291        settings.defaults.tab_size = NonZeroU32::new(3)
 5292    });
 5293
 5294    let mut cx = EditorTestContext::new(cx).await;
 5295
 5296    // MULTI SELECTION
 5297    // Ln.1 "«" tests empty lines
 5298    // Ln.11 tests just leading whitespace
 5299    cx.set_state(indoc! {"
 5300        «
 5301        abˇ»ˇc                 // No indentation
 5302         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5303          abc  «             // 2 spaces (< 3 so dont convert)
 5304           abc              // 3 spaces (convert)
 5305             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5306        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5307        «\t abc              // Tab followed by space
 5308         \tabc              // Space followed by tab (should be consumed due to tab)
 5309        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5310           \tˇ»  «\t
 5311           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5312    "});
 5313    cx.update_editor(|e, window, cx| {
 5314        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5315    });
 5316    cx.assert_editor_state(indoc! {"
 5317        «
 5318        abc                 // No indentation
 5319         abc                // 1 space (< 3 so dont convert)
 5320          abc               // 2 spaces (< 3 so dont convert)
 5321        \tabc              // 3 spaces (convert)
 5322        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5323        \t\t\tabc           // Already tab indented
 5324        \t abc              // Tab followed by space
 5325        \tabc              // Space followed by tab (should be consumed due to tab)
 5326        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5327        \t\t\t
 5328        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5329    "});
 5330
 5331    // Test on just a few lines, the other should remain unchanged
 5332    // Only lines (4, 8, 11, 12) should change
 5333    cx.set_state(
 5334        indoc! {"
 5335            ·
 5336            abc                 // No indentation
 5337             abc                // 1 space (< 3 so dont convert)
 5338              abc               // 2 spaces (< 3 so dont convert)
 5339            «   abc              // 3 spaces (convert)ˇ»
 5340                 abc            // 5 spaces (1 tab + 2 spaces)
 5341            \t\t\tabc           // Already tab indented
 5342            \t abc              // Tab followed by space
 5343             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5344               \t\t  \tabc      // Mixed indentation
 5345            \t \t  \t   \tabc   // Mixed indentation
 5346               \t  \tˇ
 5347            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5348        "}
 5349        .replace("·", "")
 5350        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5351    );
 5352    cx.update_editor(|e, window, cx| {
 5353        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5354    });
 5355    cx.assert_editor_state(
 5356        indoc! {"
 5357            ·
 5358            abc                 // No indentation
 5359             abc                // 1 space (< 3 so dont convert)
 5360              abc               // 2 spaces (< 3 so dont convert)
 5361            «\tabc              // 3 spaces (convert)ˇ»
 5362                 abc            // 5 spaces (1 tab + 2 spaces)
 5363            \t\t\tabc           // Already tab indented
 5364            \t abc              // Tab followed by space
 5365            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5366               \t\t  \tabc      // Mixed indentation
 5367            \t \t  \t   \tabc   // Mixed indentation
 5368            «\t\t\t
 5369            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5370        "}
 5371        .replace("·", "")
 5372        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5373    );
 5374
 5375    // SINGLE SELECTION
 5376    // Ln.1 "«" tests empty lines
 5377    // Ln.11 tests just leading whitespace
 5378    cx.set_state(indoc! {"
 5379        «
 5380        abc                 // No indentation
 5381         abc                // 1 space (< 3 so dont convert)
 5382          abc               // 2 spaces (< 3 so dont convert)
 5383           abc              // 3 spaces (convert)
 5384             abc            // 5 spaces (1 tab + 2 spaces)
 5385        \t\t\tabc           // Already tab indented
 5386        \t abc              // Tab followed by space
 5387         \tabc              // Space followed by tab (should be consumed due to tab)
 5388        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5389           \t  \t
 5390           abc   \t         // Only the leading spaces should be convertedˇ»
 5391    "});
 5392    cx.update_editor(|e, window, cx| {
 5393        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5394    });
 5395    cx.assert_editor_state(indoc! {"
 5396        «
 5397        abc                 // No indentation
 5398         abc                // 1 space (< 3 so dont convert)
 5399          abc               // 2 spaces (< 3 so dont convert)
 5400        \tabc              // 3 spaces (convert)
 5401        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5402        \t\t\tabc           // Already tab indented
 5403        \t abc              // Tab followed by space
 5404        \tabc              // Space followed by tab (should be consumed due to tab)
 5405        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5406        \t\t\t
 5407        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5408    "});
 5409}
 5410
 5411#[gpui::test]
 5412async fn test_toggle_case(cx: &mut TestAppContext) {
 5413    init_test(cx, |_| {});
 5414
 5415    let mut cx = EditorTestContext::new(cx).await;
 5416
 5417    // If all lower case -> upper case
 5418    cx.set_state(indoc! {"
 5419        «hello worldˇ»
 5420    "});
 5421    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5422    cx.assert_editor_state(indoc! {"
 5423        «HELLO WORLDˇ»
 5424    "});
 5425
 5426    // If all upper case -> lower case
 5427    cx.set_state(indoc! {"
 5428        «HELLO WORLDˇ»
 5429    "});
 5430    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5431    cx.assert_editor_state(indoc! {"
 5432        «hello worldˇ»
 5433    "});
 5434
 5435    // If any upper case characters are identified -> lower case
 5436    // This matches JetBrains IDEs
 5437    cx.set_state(indoc! {"
 5438        «hEllo worldˇ»
 5439    "});
 5440    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5441    cx.assert_editor_state(indoc! {"
 5442        «hello worldˇ»
 5443    "});
 5444}
 5445
 5446#[gpui::test]
 5447async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5448    init_test(cx, |_| {});
 5449
 5450    let mut cx = EditorTestContext::new(cx).await;
 5451
 5452    cx.set_state(indoc! {"
 5453        «implement-windows-supportˇ»
 5454    "});
 5455    cx.update_editor(|e, window, cx| {
 5456        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5457    });
 5458    cx.assert_editor_state(indoc! {"
 5459        «Implement windows supportˇ»
 5460    "});
 5461}
 5462
 5463#[gpui::test]
 5464async fn test_manipulate_text(cx: &mut TestAppContext) {
 5465    init_test(cx, |_| {});
 5466
 5467    let mut cx = EditorTestContext::new(cx).await;
 5468
 5469    // Test convert_to_upper_case()
 5470    cx.set_state(indoc! {"
 5471        «hello worldˇ»
 5472    "});
 5473    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5474    cx.assert_editor_state(indoc! {"
 5475        «HELLO WORLDˇ»
 5476    "});
 5477
 5478    // Test convert_to_lower_case()
 5479    cx.set_state(indoc! {"
 5480        «HELLO WORLDˇ»
 5481    "});
 5482    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5483    cx.assert_editor_state(indoc! {"
 5484        «hello worldˇ»
 5485    "});
 5486
 5487    // Test multiple line, single selection case
 5488    cx.set_state(indoc! {"
 5489        «The quick brown
 5490        fox jumps over
 5491        the lazy dogˇ»
 5492    "});
 5493    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5494    cx.assert_editor_state(indoc! {"
 5495        «The Quick Brown
 5496        Fox Jumps Over
 5497        The Lazy Dogˇ»
 5498    "});
 5499
 5500    // Test multiple line, single selection case
 5501    cx.set_state(indoc! {"
 5502        «The quick brown
 5503        fox jumps over
 5504        the lazy dogˇ»
 5505    "});
 5506    cx.update_editor(|e, window, cx| {
 5507        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5508    });
 5509    cx.assert_editor_state(indoc! {"
 5510        «TheQuickBrown
 5511        FoxJumpsOver
 5512        TheLazyDogˇ»
 5513    "});
 5514
 5515    // From here on out, test more complex cases of manipulate_text()
 5516
 5517    // Test no selection case - should affect words cursors are in
 5518    // Cursor at beginning, middle, and end of word
 5519    cx.set_state(indoc! {"
 5520        ˇhello big beauˇtiful worldˇ
 5521    "});
 5522    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5523    cx.assert_editor_state(indoc! {"
 5524        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5525    "});
 5526
 5527    // Test multiple selections on a single line and across multiple lines
 5528    cx.set_state(indoc! {"
 5529        «Theˇ» quick «brown
 5530        foxˇ» jumps «overˇ»
 5531        the «lazyˇ» dog
 5532    "});
 5533    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5534    cx.assert_editor_state(indoc! {"
 5535        «THEˇ» quick «BROWN
 5536        FOXˇ» jumps «OVERˇ»
 5537        the «LAZYˇ» dog
 5538    "});
 5539
 5540    // Test case where text length grows
 5541    cx.set_state(indoc! {"
 5542        «tschüߡ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «TSCHÜSSˇ»
 5547    "});
 5548
 5549    // Test to make sure we don't crash when text shrinks
 5550    cx.set_state(indoc! {"
 5551        aaa_bbbˇ
 5552    "});
 5553    cx.update_editor(|e, window, cx| {
 5554        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5555    });
 5556    cx.assert_editor_state(indoc! {"
 5557        «aaaBbbˇ»
 5558    "});
 5559
 5560    // Test to make sure we all aware of the fact that each word can grow and shrink
 5561    // Final selections should be aware of this fact
 5562    cx.set_state(indoc! {"
 5563        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5564    "});
 5565    cx.update_editor(|e, window, cx| {
 5566        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5567    });
 5568    cx.assert_editor_state(indoc! {"
 5569        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5570    "});
 5571
 5572    cx.set_state(indoc! {"
 5573        «hElLo, WoRld!ˇ»
 5574    "});
 5575    cx.update_editor(|e, window, cx| {
 5576        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5577    });
 5578    cx.assert_editor_state(indoc! {"
 5579        «HeLlO, wOrLD!ˇ»
 5580    "});
 5581
 5582    // Test selections with `line_mode() = true`.
 5583    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5584    cx.set_state(indoc! {"
 5585        «The quick brown
 5586        fox jumps over
 5587        tˇ»he lazy dog
 5588    "});
 5589    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5590    cx.assert_editor_state(indoc! {"
 5591        «THE QUICK BROWN
 5592        FOX JUMPS OVER
 5593        THE LAZY DOGˇ»
 5594    "});
 5595}
 5596
 5597#[gpui::test]
 5598fn test_duplicate_line(cx: &mut TestAppContext) {
 5599    init_test(cx, |_| {});
 5600
 5601    let editor = cx.add_window(|window, cx| {
 5602        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5603        build_editor(buffer, window, cx)
 5604    });
 5605    _ = editor.update(cx, |editor, window, cx| {
 5606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5607            s.select_display_ranges([
 5608                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5609                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5610                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5611                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5612            ])
 5613        });
 5614        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5615        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5616        assert_eq!(
 5617            editor.selections.display_ranges(cx),
 5618            vec![
 5619                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5621                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5622                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5623            ]
 5624        );
 5625    });
 5626
 5627    let editor = cx.add_window(|window, cx| {
 5628        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5629        build_editor(buffer, window, cx)
 5630    });
 5631    _ = editor.update(cx, |editor, window, cx| {
 5632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5633            s.select_display_ranges([
 5634                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5635                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5636            ])
 5637        });
 5638        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5639        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5640        assert_eq!(
 5641            editor.selections.display_ranges(cx),
 5642            vec![
 5643                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5644                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5645            ]
 5646        );
 5647    });
 5648
 5649    // With `duplicate_line_up` the selections move to the duplicated lines,
 5650    // which are inserted above the original lines
 5651    let editor = cx.add_window(|window, cx| {
 5652        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5653        build_editor(buffer, window, cx)
 5654    });
 5655    _ = editor.update(cx, |editor, window, cx| {
 5656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5657            s.select_display_ranges([
 5658                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5659                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5660                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5661                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5662            ])
 5663        });
 5664        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5665        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5666        assert_eq!(
 5667            editor.selections.display_ranges(cx),
 5668            vec![
 5669                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5670                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5671                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5672                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5673            ]
 5674        );
 5675    });
 5676
 5677    let editor = cx.add_window(|window, cx| {
 5678        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5679        build_editor(buffer, window, cx)
 5680    });
 5681    _ = editor.update(cx, |editor, window, cx| {
 5682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5683            s.select_display_ranges([
 5684                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5685                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5686            ])
 5687        });
 5688        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5689        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5690        assert_eq!(
 5691            editor.selections.display_ranges(cx),
 5692            vec![
 5693                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5694                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5695            ]
 5696        );
 5697    });
 5698
 5699    let editor = cx.add_window(|window, cx| {
 5700        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5701        build_editor(buffer, window, cx)
 5702    });
 5703    _ = editor.update(cx, |editor, window, cx| {
 5704        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5705            s.select_display_ranges([
 5706                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5708            ])
 5709        });
 5710        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5711        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5712        assert_eq!(
 5713            editor.selections.display_ranges(cx),
 5714            vec![
 5715                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5716                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5717            ]
 5718        );
 5719    });
 5720}
 5721
 5722#[gpui::test]
 5723fn test_move_line_up_down(cx: &mut TestAppContext) {
 5724    init_test(cx, |_| {});
 5725
 5726    let editor = cx.add_window(|window, cx| {
 5727        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5728        build_editor(buffer, window, cx)
 5729    });
 5730    _ = editor.update(cx, |editor, window, cx| {
 5731        editor.fold_creases(
 5732            vec![
 5733                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5734                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5735                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5736            ],
 5737            true,
 5738            window,
 5739            cx,
 5740        );
 5741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5742            s.select_display_ranges([
 5743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5744                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5745                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5746                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5747            ])
 5748        });
 5749        assert_eq!(
 5750            editor.display_text(cx),
 5751            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5752        );
 5753
 5754        editor.move_line_up(&MoveLineUp, window, cx);
 5755        assert_eq!(
 5756            editor.display_text(cx),
 5757            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5758        );
 5759        assert_eq!(
 5760            editor.selections.display_ranges(cx),
 5761            vec![
 5762                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5763                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5764                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5765                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5766            ]
 5767        );
 5768    });
 5769
 5770    _ = editor.update(cx, |editor, window, cx| {
 5771        editor.move_line_down(&MoveLineDown, window, cx);
 5772        assert_eq!(
 5773            editor.display_text(cx),
 5774            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5775        );
 5776        assert_eq!(
 5777            editor.selections.display_ranges(cx),
 5778            vec![
 5779                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5780                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5781                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5782                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5783            ]
 5784        );
 5785    });
 5786
 5787    _ = editor.update(cx, |editor, window, cx| {
 5788        editor.move_line_down(&MoveLineDown, window, cx);
 5789        assert_eq!(
 5790            editor.display_text(cx),
 5791            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5792        );
 5793        assert_eq!(
 5794            editor.selections.display_ranges(cx),
 5795            vec![
 5796                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5797                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5798                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5799                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5800            ]
 5801        );
 5802    });
 5803
 5804    _ = editor.update(cx, |editor, window, cx| {
 5805        editor.move_line_up(&MoveLineUp, window, cx);
 5806        assert_eq!(
 5807            editor.display_text(cx),
 5808            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5809        );
 5810        assert_eq!(
 5811            editor.selections.display_ranges(cx),
 5812            vec![
 5813                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5814                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5815                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5816                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5817            ]
 5818        );
 5819    });
 5820}
 5821
 5822#[gpui::test]
 5823fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5824    init_test(cx, |_| {});
 5825    let editor = cx.add_window(|window, cx| {
 5826        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5827        build_editor(buffer, window, cx)
 5828    });
 5829    _ = editor.update(cx, |editor, window, cx| {
 5830        editor.fold_creases(
 5831            vec![Crease::simple(
 5832                Point::new(6, 4)..Point::new(7, 4),
 5833                FoldPlaceholder::test(),
 5834            )],
 5835            true,
 5836            window,
 5837            cx,
 5838        );
 5839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5840            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5841        });
 5842        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5843        editor.move_line_up(&MoveLineUp, window, cx);
 5844        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5845        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5846    });
 5847}
 5848
 5849#[gpui::test]
 5850fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5851    init_test(cx, |_| {});
 5852
 5853    let editor = cx.add_window(|window, cx| {
 5854        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5855        build_editor(buffer, window, cx)
 5856    });
 5857    _ = editor.update(cx, |editor, window, cx| {
 5858        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5859        editor.insert_blocks(
 5860            [BlockProperties {
 5861                style: BlockStyle::Fixed,
 5862                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5863                height: Some(1),
 5864                render: Arc::new(|_| div().into_any()),
 5865                priority: 0,
 5866            }],
 5867            Some(Autoscroll::fit()),
 5868            cx,
 5869        );
 5870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5871            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5872        });
 5873        editor.move_line_down(&MoveLineDown, window, cx);
 5874    });
 5875}
 5876
 5877#[gpui::test]
 5878async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5879    init_test(cx, |_| {});
 5880
 5881    let mut cx = EditorTestContext::new(cx).await;
 5882    cx.set_state(
 5883        &"
 5884            ˇzero
 5885            one
 5886            two
 5887            three
 5888            four
 5889            five
 5890        "
 5891        .unindent(),
 5892    );
 5893
 5894    // Create a four-line block that replaces three lines of text.
 5895    cx.update_editor(|editor, window, cx| {
 5896        let snapshot = editor.snapshot(window, cx);
 5897        let snapshot = &snapshot.buffer_snapshot();
 5898        let placement = BlockPlacement::Replace(
 5899            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5900        );
 5901        editor.insert_blocks(
 5902            [BlockProperties {
 5903                placement,
 5904                height: Some(4),
 5905                style: BlockStyle::Sticky,
 5906                render: Arc::new(|_| gpui::div().into_any_element()),
 5907                priority: 0,
 5908            }],
 5909            None,
 5910            cx,
 5911        );
 5912    });
 5913
 5914    // Move down so that the cursor touches the block.
 5915    cx.update_editor(|editor, window, cx| {
 5916        editor.move_down(&Default::default(), window, cx);
 5917    });
 5918    cx.assert_editor_state(
 5919        &"
 5920            zero
 5921            «one
 5922            two
 5923            threeˇ»
 5924            four
 5925            five
 5926        "
 5927        .unindent(),
 5928    );
 5929
 5930    // Move down past the block.
 5931    cx.update_editor(|editor, window, cx| {
 5932        editor.move_down(&Default::default(), window, cx);
 5933    });
 5934    cx.assert_editor_state(
 5935        &"
 5936            zero
 5937            one
 5938            two
 5939            three
 5940            ˇfour
 5941            five
 5942        "
 5943        .unindent(),
 5944    );
 5945}
 5946
 5947#[gpui::test]
 5948fn test_transpose(cx: &mut TestAppContext) {
 5949    init_test(cx, |_| {});
 5950
 5951    _ = cx.add_window(|window, cx| {
 5952        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5953        editor.set_style(EditorStyle::default(), window, cx);
 5954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5955            s.select_ranges([1..1])
 5956        });
 5957        editor.transpose(&Default::default(), window, cx);
 5958        assert_eq!(editor.text(cx), "bac");
 5959        assert_eq!(
 5960            editor.selections.ranges(&editor.display_snapshot(cx)),
 5961            [2..2]
 5962        );
 5963
 5964        editor.transpose(&Default::default(), window, cx);
 5965        assert_eq!(editor.text(cx), "bca");
 5966        assert_eq!(
 5967            editor.selections.ranges(&editor.display_snapshot(cx)),
 5968            [3..3]
 5969        );
 5970
 5971        editor.transpose(&Default::default(), window, cx);
 5972        assert_eq!(editor.text(cx), "bac");
 5973        assert_eq!(
 5974            editor.selections.ranges(&editor.display_snapshot(cx)),
 5975            [3..3]
 5976        );
 5977
 5978        editor
 5979    });
 5980
 5981    _ = cx.add_window(|window, cx| {
 5982        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5983        editor.set_style(EditorStyle::default(), window, cx);
 5984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5985            s.select_ranges([3..3])
 5986        });
 5987        editor.transpose(&Default::default(), window, cx);
 5988        assert_eq!(editor.text(cx), "acb\nde");
 5989        assert_eq!(
 5990            editor.selections.ranges(&editor.display_snapshot(cx)),
 5991            [3..3]
 5992        );
 5993
 5994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5995            s.select_ranges([4..4])
 5996        });
 5997        editor.transpose(&Default::default(), window, cx);
 5998        assert_eq!(editor.text(cx), "acbd\ne");
 5999        assert_eq!(
 6000            editor.selections.ranges(&editor.display_snapshot(cx)),
 6001            [5..5]
 6002        );
 6003
 6004        editor.transpose(&Default::default(), window, cx);
 6005        assert_eq!(editor.text(cx), "acbde\n");
 6006        assert_eq!(
 6007            editor.selections.ranges(&editor.display_snapshot(cx)),
 6008            [6..6]
 6009        );
 6010
 6011        editor.transpose(&Default::default(), window, cx);
 6012        assert_eq!(editor.text(cx), "acbd\ne");
 6013        assert_eq!(
 6014            editor.selections.ranges(&editor.display_snapshot(cx)),
 6015            [6..6]
 6016        );
 6017
 6018        editor
 6019    });
 6020
 6021    _ = cx.add_window(|window, cx| {
 6022        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6023        editor.set_style(EditorStyle::default(), window, cx);
 6024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6025            s.select_ranges([1..1, 2..2, 4..4])
 6026        });
 6027        editor.transpose(&Default::default(), window, cx);
 6028        assert_eq!(editor.text(cx), "bacd\ne");
 6029        assert_eq!(
 6030            editor.selections.ranges(&editor.display_snapshot(cx)),
 6031            [2..2, 3..3, 5..5]
 6032        );
 6033
 6034        editor.transpose(&Default::default(), window, cx);
 6035        assert_eq!(editor.text(cx), "bcade\n");
 6036        assert_eq!(
 6037            editor.selections.ranges(&editor.display_snapshot(cx)),
 6038            [3..3, 4..4, 6..6]
 6039        );
 6040
 6041        editor.transpose(&Default::default(), window, cx);
 6042        assert_eq!(editor.text(cx), "bcda\ne");
 6043        assert_eq!(
 6044            editor.selections.ranges(&editor.display_snapshot(cx)),
 6045            [4..4, 6..6]
 6046        );
 6047
 6048        editor.transpose(&Default::default(), window, cx);
 6049        assert_eq!(editor.text(cx), "bcade\n");
 6050        assert_eq!(
 6051            editor.selections.ranges(&editor.display_snapshot(cx)),
 6052            [4..4, 6..6]
 6053        );
 6054
 6055        editor.transpose(&Default::default(), window, cx);
 6056        assert_eq!(editor.text(cx), "bcaed\n");
 6057        assert_eq!(
 6058            editor.selections.ranges(&editor.display_snapshot(cx)),
 6059            [5..5, 6..6]
 6060        );
 6061
 6062        editor
 6063    });
 6064
 6065    _ = cx.add_window(|window, cx| {
 6066        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6067        editor.set_style(EditorStyle::default(), window, cx);
 6068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6069            s.select_ranges([4..4])
 6070        });
 6071        editor.transpose(&Default::default(), window, cx);
 6072        assert_eq!(editor.text(cx), "🏀🍐✋");
 6073        assert_eq!(
 6074            editor.selections.ranges(&editor.display_snapshot(cx)),
 6075            [8..8]
 6076        );
 6077
 6078        editor.transpose(&Default::default(), window, cx);
 6079        assert_eq!(editor.text(cx), "🏀✋🍐");
 6080        assert_eq!(
 6081            editor.selections.ranges(&editor.display_snapshot(cx)),
 6082            [11..11]
 6083        );
 6084
 6085        editor.transpose(&Default::default(), window, cx);
 6086        assert_eq!(editor.text(cx), "🏀🍐✋");
 6087        assert_eq!(
 6088            editor.selections.ranges(&editor.display_snapshot(cx)),
 6089            [11..11]
 6090        );
 6091
 6092        editor
 6093    });
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_rewrap(cx: &mut TestAppContext) {
 6098    init_test(cx, |settings| {
 6099        settings.languages.0.extend([
 6100            (
 6101                "Markdown".into(),
 6102                LanguageSettingsContent {
 6103                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6104                    preferred_line_length: Some(40),
 6105                    ..Default::default()
 6106                },
 6107            ),
 6108            (
 6109                "Plain Text".into(),
 6110                LanguageSettingsContent {
 6111                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6112                    preferred_line_length: Some(40),
 6113                    ..Default::default()
 6114                },
 6115            ),
 6116            (
 6117                "C++".into(),
 6118                LanguageSettingsContent {
 6119                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6120                    preferred_line_length: Some(40),
 6121                    ..Default::default()
 6122                },
 6123            ),
 6124            (
 6125                "Python".into(),
 6126                LanguageSettingsContent {
 6127                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6128                    preferred_line_length: Some(40),
 6129                    ..Default::default()
 6130                },
 6131            ),
 6132            (
 6133                "Rust".into(),
 6134                LanguageSettingsContent {
 6135                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6136                    preferred_line_length: Some(40),
 6137                    ..Default::default()
 6138                },
 6139            ),
 6140        ])
 6141    });
 6142
 6143    let mut cx = EditorTestContext::new(cx).await;
 6144
 6145    let cpp_language = Arc::new(Language::new(
 6146        LanguageConfig {
 6147            name: "C++".into(),
 6148            line_comments: vec!["// ".into()],
 6149            ..LanguageConfig::default()
 6150        },
 6151        None,
 6152    ));
 6153    let python_language = Arc::new(Language::new(
 6154        LanguageConfig {
 6155            name: "Python".into(),
 6156            line_comments: vec!["# ".into()],
 6157            ..LanguageConfig::default()
 6158        },
 6159        None,
 6160    ));
 6161    let markdown_language = Arc::new(Language::new(
 6162        LanguageConfig {
 6163            name: "Markdown".into(),
 6164            rewrap_prefixes: vec![
 6165                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6166                regex::Regex::new("[-*+]\\s+").unwrap(),
 6167            ],
 6168            ..LanguageConfig::default()
 6169        },
 6170        None,
 6171    ));
 6172    let rust_language = Arc::new(
 6173        Language::new(
 6174            LanguageConfig {
 6175                name: "Rust".into(),
 6176                line_comments: vec!["// ".into(), "/// ".into()],
 6177                ..LanguageConfig::default()
 6178            },
 6179            Some(tree_sitter_rust::LANGUAGE.into()),
 6180        )
 6181        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6182        .unwrap(),
 6183    );
 6184
 6185    let plaintext_language = Arc::new(Language::new(
 6186        LanguageConfig {
 6187            name: "Plain Text".into(),
 6188            ..LanguageConfig::default()
 6189        },
 6190        None,
 6191    ));
 6192
 6193    // Test basic rewrapping of a long line with a cursor
 6194    assert_rewrap(
 6195        indoc! {"
 6196            // ˇThis is a long comment that needs to be wrapped.
 6197        "},
 6198        indoc! {"
 6199            // ˇThis is a long comment that needs to
 6200            // be wrapped.
 6201        "},
 6202        cpp_language.clone(),
 6203        &mut cx,
 6204    );
 6205
 6206    // Test rewrapping a full selection
 6207    assert_rewrap(
 6208        indoc! {"
 6209            «// This selected long comment needs to be wrapped.ˇ»"
 6210        },
 6211        indoc! {"
 6212            «// This selected long comment needs to
 6213            // be wrapped.ˇ»"
 6214        },
 6215        cpp_language.clone(),
 6216        &mut cx,
 6217    );
 6218
 6219    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6220    assert_rewrap(
 6221        indoc! {"
 6222            // ˇThis is the first line.
 6223            // Thisˇ is the second line.
 6224            // This is the thirdˇ line, all part of one paragraph.
 6225         "},
 6226        indoc! {"
 6227            // ˇThis is the first line. Thisˇ is the
 6228            // second line. This is the thirdˇ line,
 6229            // all part of one paragraph.
 6230         "},
 6231        cpp_language.clone(),
 6232        &mut cx,
 6233    );
 6234
 6235    // Test multiple cursors in different paragraphs trigger separate rewraps
 6236    assert_rewrap(
 6237        indoc! {"
 6238            // ˇThis is the first paragraph, first line.
 6239            // ˇThis is the first paragraph, second line.
 6240
 6241            // ˇThis is the second paragraph, first line.
 6242            // ˇThis is the second paragraph, second line.
 6243        "},
 6244        indoc! {"
 6245            // ˇThis is the first paragraph, first
 6246            // line. ˇThis is the first paragraph,
 6247            // second line.
 6248
 6249            // ˇThis is the second paragraph, first
 6250            // line. ˇThis is the second paragraph,
 6251            // second line.
 6252        "},
 6253        cpp_language.clone(),
 6254        &mut cx,
 6255    );
 6256
 6257    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6258    assert_rewrap(
 6259        indoc! {"
 6260            «// A regular long long comment to be wrapped.
 6261            /// A documentation long comment to be wrapped.ˇ»
 6262          "},
 6263        indoc! {"
 6264            «// A regular long long comment to be
 6265            // wrapped.
 6266            /// A documentation long comment to be
 6267            /// wrapped.ˇ»
 6268          "},
 6269        rust_language.clone(),
 6270        &mut cx,
 6271    );
 6272
 6273    // Test that change in indentation level trigger seperate rewraps
 6274    assert_rewrap(
 6275        indoc! {"
 6276            fn foo() {
 6277                «// This is a long comment at the base indent.
 6278                    // This is a long comment at the next indent.ˇ»
 6279            }
 6280        "},
 6281        indoc! {"
 6282            fn foo() {
 6283                «// This is a long comment at the
 6284                // base indent.
 6285                    // This is a long comment at the
 6286                    // next indent.ˇ»
 6287            }
 6288        "},
 6289        rust_language.clone(),
 6290        &mut cx,
 6291    );
 6292
 6293    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6294    assert_rewrap(
 6295        indoc! {"
 6296            # ˇThis is a long comment using a pound sign.
 6297        "},
 6298        indoc! {"
 6299            # ˇThis is a long comment using a pound
 6300            # sign.
 6301        "},
 6302        python_language,
 6303        &mut cx,
 6304    );
 6305
 6306    // Test rewrapping only affects comments, not code even when selected
 6307    assert_rewrap(
 6308        indoc! {"
 6309            «/// This doc comment is long and should be wrapped.
 6310            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6311        "},
 6312        indoc! {"
 6313            «/// This doc comment is long and should
 6314            /// be wrapped.
 6315            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6316        "},
 6317        rust_language.clone(),
 6318        &mut cx,
 6319    );
 6320
 6321    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6322    assert_rewrap(
 6323        indoc! {"
 6324            # Header
 6325
 6326            A long long long line of markdown text to wrap.ˇ
 6327         "},
 6328        indoc! {"
 6329            # Header
 6330
 6331            A long long long line of markdown text
 6332            to wrap.ˇ
 6333         "},
 6334        markdown_language.clone(),
 6335        &mut cx,
 6336    );
 6337
 6338    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6339    assert_rewrap(
 6340        indoc! {"
 6341            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6342            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6343            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6344        "},
 6345        indoc! {"
 6346            «1. This is a numbered list item that is
 6347               very long and needs to be wrapped
 6348               properly.
 6349            2. This is a numbered list item that is
 6350               very long and needs to be wrapped
 6351               properly.
 6352            - This is an unordered list item that is
 6353              also very long and should not merge
 6354              with the numbered item.ˇ»
 6355        "},
 6356        markdown_language.clone(),
 6357        &mut cx,
 6358    );
 6359
 6360    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6361    assert_rewrap(
 6362        indoc! {"
 6363            «1. This is a numbered list item that is
 6364            very long and needs to be wrapped
 6365            properly.
 6366            2. This is a numbered list item that is
 6367            very long and needs to be wrapped
 6368            properly.
 6369            - This is an unordered list item that is
 6370            also very long and should not merge with
 6371            the numbered item.ˇ»
 6372        "},
 6373        indoc! {"
 6374            «1. This is a numbered list item that is
 6375               very long and needs to be wrapped
 6376               properly.
 6377            2. This is a numbered list item that is
 6378               very long and needs to be wrapped
 6379               properly.
 6380            - This is an unordered list item that is
 6381              also very long and should not merge
 6382              with the numbered item.ˇ»
 6383        "},
 6384        markdown_language.clone(),
 6385        &mut cx,
 6386    );
 6387
 6388    // Test that rewrapping maintain indents even when they already exists.
 6389    assert_rewrap(
 6390        indoc! {"
 6391            «1. This is a numbered list
 6392               item that is very long and needs to be wrapped properly.
 6393            2. This is a numbered list
 6394               item that is very long and needs to be wrapped properly.
 6395            - This is an unordered list item that is also very long and
 6396              should not merge with the numbered item.ˇ»
 6397        "},
 6398        indoc! {"
 6399            «1. This is a numbered list item that is
 6400               very long and needs to be wrapped
 6401               properly.
 6402            2. This is a numbered list item that is
 6403               very long and needs to be wrapped
 6404               properly.
 6405            - This is an unordered list item that is
 6406              also very long and should not merge
 6407              with the numbered item.ˇ»
 6408        "},
 6409        markdown_language,
 6410        &mut cx,
 6411    );
 6412
 6413    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6414    assert_rewrap(
 6415        indoc! {"
 6416            ˇThis is a very long line of plain text that will be wrapped.
 6417        "},
 6418        indoc! {"
 6419            ˇThis is a very long line of plain text
 6420            that will be wrapped.
 6421        "},
 6422        plaintext_language.clone(),
 6423        &mut cx,
 6424    );
 6425
 6426    // Test that non-commented code acts as a paragraph boundary within a selection
 6427    assert_rewrap(
 6428        indoc! {"
 6429               «// This is the first long comment block to be wrapped.
 6430               fn my_func(a: u32);
 6431               // This is the second long comment block to be wrapped.ˇ»
 6432           "},
 6433        indoc! {"
 6434               «// This is the first long comment block
 6435               // to be wrapped.
 6436               fn my_func(a: u32);
 6437               // This is the second long comment block
 6438               // to be wrapped.ˇ»
 6439           "},
 6440        rust_language,
 6441        &mut cx,
 6442    );
 6443
 6444    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6445    assert_rewrap(
 6446        indoc! {"
 6447            «ˇThis is a very long line that will be wrapped.
 6448
 6449            This is another paragraph in the same selection.»
 6450
 6451            «\tThis is a very long indented line that will be wrapped.ˇ»
 6452         "},
 6453        indoc! {"
 6454            «ˇThis is a very long line that will be
 6455            wrapped.
 6456
 6457            This is another paragraph in the same
 6458            selection.»
 6459
 6460            «\tThis is a very long indented line
 6461            \tthat will be wrapped.ˇ»
 6462         "},
 6463        plaintext_language,
 6464        &mut cx,
 6465    );
 6466
 6467    // Test that an empty comment line acts as a paragraph boundary
 6468    assert_rewrap(
 6469        indoc! {"
 6470            // ˇThis is a long comment that will be wrapped.
 6471            //
 6472            // And this is another long comment that will also be wrapped.ˇ
 6473         "},
 6474        indoc! {"
 6475            // ˇThis is a long comment that will be
 6476            // wrapped.
 6477            //
 6478            // And this is another long comment that
 6479            // will also be wrapped.ˇ
 6480         "},
 6481        cpp_language,
 6482        &mut cx,
 6483    );
 6484
 6485    #[track_caller]
 6486    fn assert_rewrap(
 6487        unwrapped_text: &str,
 6488        wrapped_text: &str,
 6489        language: Arc<Language>,
 6490        cx: &mut EditorTestContext,
 6491    ) {
 6492        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6493        cx.set_state(unwrapped_text);
 6494        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6495        cx.assert_editor_state(wrapped_text);
 6496    }
 6497}
 6498
 6499#[gpui::test]
 6500async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6501    init_test(cx, |settings| {
 6502        settings.languages.0.extend([(
 6503            "Rust".into(),
 6504            LanguageSettingsContent {
 6505                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6506                preferred_line_length: Some(40),
 6507                ..Default::default()
 6508            },
 6509        )])
 6510    });
 6511
 6512    let mut cx = EditorTestContext::new(cx).await;
 6513
 6514    let rust_lang = Arc::new(
 6515        Language::new(
 6516            LanguageConfig {
 6517                name: "Rust".into(),
 6518                line_comments: vec!["// ".into()],
 6519                block_comment: Some(BlockCommentConfig {
 6520                    start: "/*".into(),
 6521                    end: "*/".into(),
 6522                    prefix: "* ".into(),
 6523                    tab_size: 1,
 6524                }),
 6525                documentation_comment: Some(BlockCommentConfig {
 6526                    start: "/**".into(),
 6527                    end: "*/".into(),
 6528                    prefix: "* ".into(),
 6529                    tab_size: 1,
 6530                }),
 6531
 6532                ..LanguageConfig::default()
 6533            },
 6534            Some(tree_sitter_rust::LANGUAGE.into()),
 6535        )
 6536        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6537        .unwrap(),
 6538    );
 6539
 6540    // regular block comment
 6541    assert_rewrap(
 6542        indoc! {"
 6543            /*
 6544             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6545             */
 6546            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6547        "},
 6548        indoc! {"
 6549            /*
 6550             *ˇ Lorem ipsum dolor sit amet,
 6551             * consectetur adipiscing elit.
 6552             */
 6553            /*
 6554             *ˇ Lorem ipsum dolor sit amet,
 6555             * consectetur adipiscing elit.
 6556             */
 6557        "},
 6558        rust_lang.clone(),
 6559        &mut cx,
 6560    );
 6561
 6562    // indent is respected
 6563    assert_rewrap(
 6564        indoc! {"
 6565            {}
 6566                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6567        "},
 6568        indoc! {"
 6569            {}
 6570                /*
 6571                 *ˇ Lorem ipsum dolor sit amet,
 6572                 * consectetur adipiscing elit.
 6573                 */
 6574        "},
 6575        rust_lang.clone(),
 6576        &mut cx,
 6577    );
 6578
 6579    // short block comments with inline delimiters
 6580    assert_rewrap(
 6581        indoc! {"
 6582            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6583            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6584             */
 6585            /*
 6586             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6587        "},
 6588        indoc! {"
 6589            /*
 6590             *ˇ Lorem ipsum dolor sit amet,
 6591             * consectetur adipiscing elit.
 6592             */
 6593            /*
 6594             *ˇ Lorem ipsum dolor sit amet,
 6595             * consectetur adipiscing elit.
 6596             */
 6597            /*
 6598             *ˇ Lorem ipsum dolor sit amet,
 6599             * consectetur adipiscing elit.
 6600             */
 6601        "},
 6602        rust_lang.clone(),
 6603        &mut cx,
 6604    );
 6605
 6606    // multiline block comment with inline start/end delimiters
 6607    assert_rewrap(
 6608        indoc! {"
 6609            /*ˇ Lorem ipsum dolor sit amet,
 6610             * consectetur adipiscing elit. */
 6611        "},
 6612        indoc! {"
 6613            /*
 6614             *ˇ Lorem ipsum dolor sit amet,
 6615             * consectetur adipiscing elit.
 6616             */
 6617        "},
 6618        rust_lang.clone(),
 6619        &mut cx,
 6620    );
 6621
 6622    // block comment rewrap still respects paragraph bounds
 6623    assert_rewrap(
 6624        indoc! {"
 6625            /*
 6626             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6627             *
 6628             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6629             */
 6630        "},
 6631        indoc! {"
 6632            /*
 6633             *ˇ Lorem ipsum dolor sit amet,
 6634             * consectetur adipiscing elit.
 6635             *
 6636             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6637             */
 6638        "},
 6639        rust_lang.clone(),
 6640        &mut cx,
 6641    );
 6642
 6643    // documentation comments
 6644    assert_rewrap(
 6645        indoc! {"
 6646            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6647            /**
 6648             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6649             */
 6650        "},
 6651        indoc! {"
 6652            /**
 6653             *ˇ Lorem ipsum dolor sit amet,
 6654             * consectetur adipiscing elit.
 6655             */
 6656            /**
 6657             *ˇ Lorem ipsum dolor sit amet,
 6658             * consectetur adipiscing elit.
 6659             */
 6660        "},
 6661        rust_lang.clone(),
 6662        &mut cx,
 6663    );
 6664
 6665    // different, adjacent comments
 6666    assert_rewrap(
 6667        indoc! {"
 6668            /**
 6669             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6670             */
 6671            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6672            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6673        "},
 6674        indoc! {"
 6675            /**
 6676             *ˇ Lorem ipsum dolor sit amet,
 6677             * consectetur adipiscing elit.
 6678             */
 6679            /*
 6680             *ˇ Lorem ipsum dolor sit amet,
 6681             * consectetur adipiscing elit.
 6682             */
 6683            //ˇ Lorem ipsum dolor sit amet,
 6684            // consectetur adipiscing elit.
 6685        "},
 6686        rust_lang.clone(),
 6687        &mut cx,
 6688    );
 6689
 6690    // selection w/ single short block comment
 6691    assert_rewrap(
 6692        indoc! {"
 6693            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6694        "},
 6695        indoc! {"
 6696            «/*
 6697             * Lorem ipsum dolor sit amet,
 6698             * consectetur adipiscing elit.
 6699             */ˇ»
 6700        "},
 6701        rust_lang.clone(),
 6702        &mut cx,
 6703    );
 6704
 6705    // rewrapping a single comment w/ abutting comments
 6706    assert_rewrap(
 6707        indoc! {"
 6708            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6709            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6710        "},
 6711        indoc! {"
 6712            /*
 6713             * ˇLorem ipsum dolor sit amet,
 6714             * consectetur adipiscing elit.
 6715             */
 6716            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6717        "},
 6718        rust_lang.clone(),
 6719        &mut cx,
 6720    );
 6721
 6722    // selection w/ non-abutting short block comments
 6723    assert_rewrap(
 6724        indoc! {"
 6725            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6726
 6727            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6728        "},
 6729        indoc! {"
 6730            «/*
 6731             * Lorem ipsum dolor sit amet,
 6732             * consectetur adipiscing elit.
 6733             */
 6734
 6735            /*
 6736             * Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */ˇ»
 6739        "},
 6740        rust_lang.clone(),
 6741        &mut cx,
 6742    );
 6743
 6744    // selection of multiline block comments
 6745    assert_rewrap(
 6746        indoc! {"
 6747            «/* Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit. */ˇ»
 6749        "},
 6750        indoc! {"
 6751            «/*
 6752             * Lorem ipsum dolor sit amet,
 6753             * consectetur adipiscing elit.
 6754             */ˇ»
 6755        "},
 6756        rust_lang.clone(),
 6757        &mut cx,
 6758    );
 6759
 6760    // partial selection of multiline block comments
 6761    assert_rewrap(
 6762        indoc! {"
 6763            «/* Lorem ipsum dolor sit amet,ˇ»
 6764             * consectetur adipiscing elit. */
 6765            /* Lorem ipsum dolor sit amet,
 6766             «* consectetur adipiscing elit. */ˇ»
 6767        "},
 6768        indoc! {"
 6769            «/*
 6770             * Lorem ipsum dolor sit amet,ˇ»
 6771             * consectetur adipiscing elit. */
 6772            /* Lorem ipsum dolor sit amet,
 6773             «* consectetur adipiscing elit.
 6774             */ˇ»
 6775        "},
 6776        rust_lang.clone(),
 6777        &mut cx,
 6778    );
 6779
 6780    // selection w/ abutting short block comments
 6781    // TODO: should not be combined; should rewrap as 2 comments
 6782    assert_rewrap(
 6783        indoc! {"
 6784            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6785            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6786        "},
 6787        // desired behavior:
 6788        // indoc! {"
 6789        //     «/*
 6790        //      * Lorem ipsum dolor sit amet,
 6791        //      * consectetur adipiscing elit.
 6792        //      */
 6793        //     /*
 6794        //      * Lorem ipsum dolor sit amet,
 6795        //      * consectetur adipiscing elit.
 6796        //      */ˇ»
 6797        // "},
 6798        // actual behaviour:
 6799        indoc! {"
 6800            «/*
 6801             * Lorem ipsum dolor sit amet,
 6802             * consectetur adipiscing elit. Lorem
 6803             * ipsum dolor sit amet, consectetur
 6804             * adipiscing elit.
 6805             */ˇ»
 6806        "},
 6807        rust_lang.clone(),
 6808        &mut cx,
 6809    );
 6810
 6811    // TODO: same as above, but with delimiters on separate line
 6812    // assert_rewrap(
 6813    //     indoc! {"
 6814    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6815    //          */
 6816    //         /*
 6817    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6818    //     "},
 6819    //     // desired:
 6820    //     // indoc! {"
 6821    //     //     «/*
 6822    //     //      * Lorem ipsum dolor sit amet,
 6823    //     //      * consectetur adipiscing elit.
 6824    //     //      */
 6825    //     //     /*
 6826    //     //      * Lorem ipsum dolor sit amet,
 6827    //     //      * consectetur adipiscing elit.
 6828    //     //      */ˇ»
 6829    //     // "},
 6830    //     // actual: (but with trailing w/s on the empty lines)
 6831    //     indoc! {"
 6832    //         «/*
 6833    //          * Lorem ipsum dolor sit amet,
 6834    //          * consectetur adipiscing elit.
 6835    //          *
 6836    //          */
 6837    //         /*
 6838    //          *
 6839    //          * Lorem ipsum dolor sit amet,
 6840    //          * consectetur adipiscing elit.
 6841    //          */ˇ»
 6842    //     "},
 6843    //     rust_lang.clone(),
 6844    //     &mut cx,
 6845    // );
 6846
 6847    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6848    assert_rewrap(
 6849        indoc! {"
 6850            /*
 6851             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6852             */
 6853            /*
 6854             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6855            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6856        "},
 6857        // desired:
 6858        // indoc! {"
 6859        //     /*
 6860        //      *ˇ Lorem ipsum dolor sit amet,
 6861        //      * consectetur adipiscing elit.
 6862        //      */
 6863        //     /*
 6864        //      *ˇ Lorem ipsum dolor sit amet,
 6865        //      * consectetur adipiscing elit.
 6866        //      */
 6867        //     /*
 6868        //      *ˇ Lorem ipsum dolor sit amet
 6869        //      */ /* consectetur adipiscing elit. */
 6870        // "},
 6871        // actual:
 6872        indoc! {"
 6873            /*
 6874             //ˇ Lorem ipsum dolor sit amet,
 6875             // consectetur adipiscing elit.
 6876             */
 6877            /*
 6878             * //ˇ Lorem ipsum dolor sit amet,
 6879             * consectetur adipiscing elit.
 6880             */
 6881            /*
 6882             *ˇ Lorem ipsum dolor sit amet */ /*
 6883             * consectetur adipiscing elit.
 6884             */
 6885        "},
 6886        rust_lang,
 6887        &mut cx,
 6888    );
 6889
 6890    #[track_caller]
 6891    fn assert_rewrap(
 6892        unwrapped_text: &str,
 6893        wrapped_text: &str,
 6894        language: Arc<Language>,
 6895        cx: &mut EditorTestContext,
 6896    ) {
 6897        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6898        cx.set_state(unwrapped_text);
 6899        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6900        cx.assert_editor_state(wrapped_text);
 6901    }
 6902}
 6903
 6904#[gpui::test]
 6905async fn test_hard_wrap(cx: &mut TestAppContext) {
 6906    init_test(cx, |_| {});
 6907    let mut cx = EditorTestContext::new(cx).await;
 6908
 6909    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6910    cx.update_editor(|editor, _, cx| {
 6911        editor.set_hard_wrap(Some(14), cx);
 6912    });
 6913
 6914    cx.set_state(indoc!(
 6915        "
 6916        one two three ˇ
 6917        "
 6918    ));
 6919    cx.simulate_input("four");
 6920    cx.run_until_parked();
 6921
 6922    cx.assert_editor_state(indoc!(
 6923        "
 6924        one two three
 6925        fourˇ
 6926        "
 6927    ));
 6928
 6929    cx.update_editor(|editor, window, cx| {
 6930        editor.newline(&Default::default(), window, cx);
 6931    });
 6932    cx.run_until_parked();
 6933    cx.assert_editor_state(indoc!(
 6934        "
 6935        one two three
 6936        four
 6937        ˇ
 6938        "
 6939    ));
 6940
 6941    cx.simulate_input("five");
 6942    cx.run_until_parked();
 6943    cx.assert_editor_state(indoc!(
 6944        "
 6945        one two three
 6946        four
 6947        fiveˇ
 6948        "
 6949    ));
 6950
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.newline(&Default::default(), window, cx);
 6953    });
 6954    cx.run_until_parked();
 6955    cx.simulate_input("# ");
 6956    cx.run_until_parked();
 6957    cx.assert_editor_state(indoc!(
 6958        "
 6959        one two three
 6960        four
 6961        five
 6962        # ˇ
 6963        "
 6964    ));
 6965
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.newline(&Default::default(), window, cx);
 6968    });
 6969    cx.run_until_parked();
 6970    cx.assert_editor_state(indoc!(
 6971        "
 6972        one two three
 6973        four
 6974        five
 6975        #\x20
 6976 6977        "
 6978    ));
 6979
 6980    cx.simulate_input(" 6");
 6981    cx.run_until_parked();
 6982    cx.assert_editor_state(indoc!(
 6983        "
 6984        one two three
 6985        four
 6986        five
 6987        #
 6988        # 6ˇ
 6989        "
 6990    ));
 6991}
 6992
 6993#[gpui::test]
 6994async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6995    init_test(cx, |_| {});
 6996
 6997    let mut cx = EditorTestContext::new(cx).await;
 6998
 6999    cx.set_state(indoc! {"The quick brownˇ"});
 7000    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7001    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7002
 7003    cx.set_state(indoc! {"The emacs foxˇ"});
 7004    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7005    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7006
 7007    cx.set_state(indoc! {"
 7008        The quick« brownˇ»
 7009        fox jumps overˇ
 7010        the lazy dog"});
 7011    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7012    cx.assert_editor_state(indoc! {"
 7013        The quickˇ
 7014        ˇthe lazy dog"});
 7015
 7016    cx.set_state(indoc! {"
 7017        The quick« brownˇ»
 7018        fox jumps overˇ
 7019        the lazy dog"});
 7020    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7021    cx.assert_editor_state(indoc! {"
 7022        The quickˇ
 7023        fox jumps overˇthe lazy dog"});
 7024
 7025    cx.set_state(indoc! {"
 7026        The quick« brownˇ»
 7027        fox jumps overˇ
 7028        the lazy dog"});
 7029    cx.update_editor(|e, window, cx| {
 7030        e.cut_to_end_of_line(
 7031            &CutToEndOfLine {
 7032                stop_at_newlines: true,
 7033            },
 7034            window,
 7035            cx,
 7036        )
 7037    });
 7038    cx.assert_editor_state(indoc! {"
 7039        The quickˇ
 7040        fox jumps overˇ
 7041        the lazy dog"});
 7042
 7043    cx.set_state(indoc! {"
 7044        The quick« brownˇ»
 7045        fox jumps overˇ
 7046        the lazy dog"});
 7047    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7048    cx.assert_editor_state(indoc! {"
 7049        The quickˇ
 7050        fox jumps overˇthe lazy dog"});
 7051}
 7052
 7053#[gpui::test]
 7054async fn test_clipboard(cx: &mut TestAppContext) {
 7055    init_test(cx, |_| {});
 7056
 7057    let mut cx = EditorTestContext::new(cx).await;
 7058
 7059    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7060    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7061    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7062
 7063    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7064    cx.set_state("two ˇfour ˇsix ˇ");
 7065    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7066    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7067
 7068    // Paste again but with only two cursors. Since the number of cursors doesn't
 7069    // match the number of slices in the clipboard, the entire clipboard text
 7070    // is pasted at each cursor.
 7071    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7072    cx.update_editor(|e, window, cx| {
 7073        e.handle_input("( ", window, cx);
 7074        e.paste(&Paste, window, cx);
 7075        e.handle_input(") ", window, cx);
 7076    });
 7077    cx.assert_editor_state(
 7078        &([
 7079            "( one✅ ",
 7080            "three ",
 7081            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7082            "three ",
 7083            "five ) ˇ",
 7084        ]
 7085        .join("\n")),
 7086    );
 7087
 7088    // Cut with three selections, one of which is full-line.
 7089    cx.set_state(indoc! {"
 7090        1«2ˇ»3
 7091        4ˇ567
 7092        «8ˇ»9"});
 7093    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7094    cx.assert_editor_state(indoc! {"
 7095        1ˇ3
 7096        ˇ9"});
 7097
 7098    // Paste with three selections, noticing how the copied selection that was full-line
 7099    // gets inserted before the second cursor.
 7100    cx.set_state(indoc! {"
 7101        1ˇ3
 7102 7103        «oˇ»ne"});
 7104    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7105    cx.assert_editor_state(indoc! {"
 7106        12ˇ3
 7107        4567
 7108 7109        8ˇne"});
 7110
 7111    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7112    cx.set_state(indoc! {"
 7113        The quick brown
 7114        fox juˇmps over
 7115        the lazy dog"});
 7116    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7117    assert_eq!(
 7118        cx.read_from_clipboard()
 7119            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7120        Some("fox jumps over\n".to_string())
 7121    );
 7122
 7123    // Paste with three selections, noticing how the copied full-line selection is inserted
 7124    // before the empty selections but replaces the selection that is non-empty.
 7125    cx.set_state(indoc! {"
 7126        Tˇhe quick brown
 7127        «foˇ»x jumps over
 7128        tˇhe lazy dog"});
 7129    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7130    cx.assert_editor_state(indoc! {"
 7131        fox jumps over
 7132        Tˇhe quick brown
 7133        fox jumps over
 7134        ˇx jumps over
 7135        fox jumps over
 7136        tˇhe lazy dog"});
 7137}
 7138
 7139#[gpui::test]
 7140async fn test_copy_trim(cx: &mut TestAppContext) {
 7141    init_test(cx, |_| {});
 7142
 7143    let mut cx = EditorTestContext::new(cx).await;
 7144    cx.set_state(
 7145        r#"            «for selection in selections.iter() {
 7146            let mut start = selection.start;
 7147            let mut end = selection.end;
 7148            let is_entire_line = selection.is_empty();
 7149            if is_entire_line {
 7150                start = Point::new(start.row, 0);ˇ»
 7151                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7152            }
 7153        "#,
 7154    );
 7155    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7156    assert_eq!(
 7157        cx.read_from_clipboard()
 7158            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7159        Some(
 7160            "for selection in selections.iter() {
 7161            let mut start = selection.start;
 7162            let mut end = selection.end;
 7163            let is_entire_line = selection.is_empty();
 7164            if is_entire_line {
 7165                start = Point::new(start.row, 0);"
 7166                .to_string()
 7167        ),
 7168        "Regular copying preserves all indentation selected",
 7169    );
 7170    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7171    assert_eq!(
 7172        cx.read_from_clipboard()
 7173            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7174        Some(
 7175            "for selection in selections.iter() {
 7176let mut start = selection.start;
 7177let mut end = selection.end;
 7178let is_entire_line = selection.is_empty();
 7179if is_entire_line {
 7180    start = Point::new(start.row, 0);"
 7181                .to_string()
 7182        ),
 7183        "Copying with stripping should strip all leading whitespaces"
 7184    );
 7185
 7186    cx.set_state(
 7187        r#"       «     for selection in selections.iter() {
 7188            let mut start = selection.start;
 7189            let mut end = selection.end;
 7190            let is_entire_line = selection.is_empty();
 7191            if is_entire_line {
 7192                start = Point::new(start.row, 0);ˇ»
 7193                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7194            }
 7195        "#,
 7196    );
 7197    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7198    assert_eq!(
 7199        cx.read_from_clipboard()
 7200            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7201        Some(
 7202            "     for selection in selections.iter() {
 7203            let mut start = selection.start;
 7204            let mut end = selection.end;
 7205            let is_entire_line = selection.is_empty();
 7206            if is_entire_line {
 7207                start = Point::new(start.row, 0);"
 7208                .to_string()
 7209        ),
 7210        "Regular copying preserves all indentation selected",
 7211    );
 7212    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7213    assert_eq!(
 7214        cx.read_from_clipboard()
 7215            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7216        Some(
 7217            "for selection in selections.iter() {
 7218let mut start = selection.start;
 7219let mut end = selection.end;
 7220let is_entire_line = selection.is_empty();
 7221if is_entire_line {
 7222    start = Point::new(start.row, 0);"
 7223                .to_string()
 7224        ),
 7225        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7226    );
 7227
 7228    cx.set_state(
 7229        r#"       «ˇ     for selection in selections.iter() {
 7230            let mut start = selection.start;
 7231            let mut end = selection.end;
 7232            let is_entire_line = selection.is_empty();
 7233            if is_entire_line {
 7234                start = Point::new(start.row, 0);»
 7235                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7236            }
 7237        "#,
 7238    );
 7239    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7240    assert_eq!(
 7241        cx.read_from_clipboard()
 7242            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7243        Some(
 7244            "     for selection in selections.iter() {
 7245            let mut start = selection.start;
 7246            let mut end = selection.end;
 7247            let is_entire_line = selection.is_empty();
 7248            if is_entire_line {
 7249                start = Point::new(start.row, 0);"
 7250                .to_string()
 7251        ),
 7252        "Regular copying for reverse selection works the same",
 7253    );
 7254    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7255    assert_eq!(
 7256        cx.read_from_clipboard()
 7257            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7258        Some(
 7259            "for selection in selections.iter() {
 7260let mut start = selection.start;
 7261let mut end = selection.end;
 7262let is_entire_line = selection.is_empty();
 7263if is_entire_line {
 7264    start = Point::new(start.row, 0);"
 7265                .to_string()
 7266        ),
 7267        "Copying with stripping for reverse selection works the same"
 7268    );
 7269
 7270    cx.set_state(
 7271        r#"            for selection «in selections.iter() {
 7272            let mut start = selection.start;
 7273            let mut end = selection.end;
 7274            let is_entire_line = selection.is_empty();
 7275            if is_entire_line {
 7276                start = Point::new(start.row, 0);ˇ»
 7277                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7278            }
 7279        "#,
 7280    );
 7281    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7282    assert_eq!(
 7283        cx.read_from_clipboard()
 7284            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7285        Some(
 7286            "in selections.iter() {
 7287            let mut start = selection.start;
 7288            let mut end = selection.end;
 7289            let is_entire_line = selection.is_empty();
 7290            if is_entire_line {
 7291                start = Point::new(start.row, 0);"
 7292                .to_string()
 7293        ),
 7294        "When selecting past the indent, the copying works as usual",
 7295    );
 7296    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7297    assert_eq!(
 7298        cx.read_from_clipboard()
 7299            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7300        Some(
 7301            "in selections.iter() {
 7302            let mut start = selection.start;
 7303            let mut end = selection.end;
 7304            let is_entire_line = selection.is_empty();
 7305            if is_entire_line {
 7306                start = Point::new(start.row, 0);"
 7307                .to_string()
 7308        ),
 7309        "When selecting past the indent, nothing is trimmed"
 7310    );
 7311
 7312    cx.set_state(
 7313        r#"            «for selection in selections.iter() {
 7314            let mut start = selection.start;
 7315
 7316            let mut end = selection.end;
 7317            let is_entire_line = selection.is_empty();
 7318            if is_entire_line {
 7319                start = Point::new(start.row, 0);
 7320ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7321            }
 7322        "#,
 7323    );
 7324    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7325    assert_eq!(
 7326        cx.read_from_clipboard()
 7327            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7328        Some(
 7329            "for selection in selections.iter() {
 7330let mut start = selection.start;
 7331
 7332let mut end = selection.end;
 7333let is_entire_line = selection.is_empty();
 7334if is_entire_line {
 7335    start = Point::new(start.row, 0);
 7336"
 7337            .to_string()
 7338        ),
 7339        "Copying with stripping should ignore empty lines"
 7340    );
 7341}
 7342
 7343#[gpui::test]
 7344async fn test_paste_multiline(cx: &mut TestAppContext) {
 7345    init_test(cx, |_| {});
 7346
 7347    let mut cx = EditorTestContext::new(cx).await;
 7348    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7349
 7350    // Cut an indented block, without the leading whitespace.
 7351    cx.set_state(indoc! {"
 7352        const a: B = (
 7353            c(),
 7354            «d(
 7355                e,
 7356                f
 7357            )ˇ»
 7358        );
 7359    "});
 7360    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7361    cx.assert_editor_state(indoc! {"
 7362        const a: B = (
 7363            c(),
 7364            ˇ
 7365        );
 7366    "});
 7367
 7368    // Paste it at the same position.
 7369    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7370    cx.assert_editor_state(indoc! {"
 7371        const a: B = (
 7372            c(),
 7373            d(
 7374                e,
 7375                f
 7376 7377        );
 7378    "});
 7379
 7380    // Paste it at a line with a lower indent level.
 7381    cx.set_state(indoc! {"
 7382        ˇ
 7383        const a: B = (
 7384            c(),
 7385        );
 7386    "});
 7387    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7388    cx.assert_editor_state(indoc! {"
 7389        d(
 7390            e,
 7391            f
 7392 7393        const a: B = (
 7394            c(),
 7395        );
 7396    "});
 7397
 7398    // Cut an indented block, with the leading whitespace.
 7399    cx.set_state(indoc! {"
 7400        const a: B = (
 7401            c(),
 7402        «    d(
 7403                e,
 7404                f
 7405            )
 7406        ˇ»);
 7407    "});
 7408    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7409    cx.assert_editor_state(indoc! {"
 7410        const a: B = (
 7411            c(),
 7412        ˇ);
 7413    "});
 7414
 7415    // Paste it at the same position.
 7416    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7417    cx.assert_editor_state(indoc! {"
 7418        const a: B = (
 7419            c(),
 7420            d(
 7421                e,
 7422                f
 7423            )
 7424        ˇ);
 7425    "});
 7426
 7427    // Paste it at a line with a higher indent level.
 7428    cx.set_state(indoc! {"
 7429        const a: B = (
 7430            c(),
 7431            d(
 7432                e,
 7433 7434            )
 7435        );
 7436    "});
 7437    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7438    cx.assert_editor_state(indoc! {"
 7439        const a: B = (
 7440            c(),
 7441            d(
 7442                e,
 7443                f    d(
 7444                    e,
 7445                    f
 7446                )
 7447        ˇ
 7448            )
 7449        );
 7450    "});
 7451
 7452    // Copy an indented block, starting mid-line
 7453    cx.set_state(indoc! {"
 7454        const a: B = (
 7455            c(),
 7456            somethin«g(
 7457                e,
 7458                f
 7459            )ˇ»
 7460        );
 7461    "});
 7462    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7463
 7464    // Paste it on a line with a lower indent level
 7465    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7466    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7467    cx.assert_editor_state(indoc! {"
 7468        const a: B = (
 7469            c(),
 7470            something(
 7471                e,
 7472                f
 7473            )
 7474        );
 7475        g(
 7476            e,
 7477            f
 7478"});
 7479}
 7480
 7481#[gpui::test]
 7482async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7483    init_test(cx, |_| {});
 7484
 7485    cx.write_to_clipboard(ClipboardItem::new_string(
 7486        "    d(\n        e\n    );\n".into(),
 7487    ));
 7488
 7489    let mut cx = EditorTestContext::new(cx).await;
 7490    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7491
 7492    cx.set_state(indoc! {"
 7493        fn a() {
 7494            b();
 7495            if c() {
 7496                ˇ
 7497            }
 7498        }
 7499    "});
 7500
 7501    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7502    cx.assert_editor_state(indoc! {"
 7503        fn a() {
 7504            b();
 7505            if c() {
 7506                d(
 7507                    e
 7508                );
 7509        ˇ
 7510            }
 7511        }
 7512    "});
 7513
 7514    cx.set_state(indoc! {"
 7515        fn a() {
 7516            b();
 7517            ˇ
 7518        }
 7519    "});
 7520
 7521    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7522    cx.assert_editor_state(indoc! {"
 7523        fn a() {
 7524            b();
 7525            d(
 7526                e
 7527            );
 7528        ˇ
 7529        }
 7530    "});
 7531}
 7532
 7533#[gpui::test]
 7534fn test_select_all(cx: &mut TestAppContext) {
 7535    init_test(cx, |_| {});
 7536
 7537    let editor = cx.add_window(|window, cx| {
 7538        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7539        build_editor(buffer, window, cx)
 7540    });
 7541    _ = editor.update(cx, |editor, window, cx| {
 7542        editor.select_all(&SelectAll, window, cx);
 7543        assert_eq!(
 7544            editor.selections.display_ranges(cx),
 7545            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7546        );
 7547    });
 7548}
 7549
 7550#[gpui::test]
 7551fn test_select_line(cx: &mut TestAppContext) {
 7552    init_test(cx, |_| {});
 7553
 7554    let editor = cx.add_window(|window, cx| {
 7555        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7556        build_editor(buffer, window, cx)
 7557    });
 7558    _ = editor.update(cx, |editor, window, cx| {
 7559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7560            s.select_display_ranges([
 7561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7562                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7563                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7564                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7565            ])
 7566        });
 7567        editor.select_line(&SelectLine, window, cx);
 7568        assert_eq!(
 7569            editor.selections.display_ranges(cx),
 7570            vec![
 7571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7572                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7573            ]
 7574        );
 7575    });
 7576
 7577    _ = editor.update(cx, |editor, window, cx| {
 7578        editor.select_line(&SelectLine, window, cx);
 7579        assert_eq!(
 7580            editor.selections.display_ranges(cx),
 7581            vec![
 7582                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7583                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7584            ]
 7585        );
 7586    });
 7587
 7588    _ = editor.update(cx, |editor, window, cx| {
 7589        editor.select_line(&SelectLine, window, cx);
 7590        assert_eq!(
 7591            editor.selections.display_ranges(cx),
 7592            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7593        );
 7594    });
 7595}
 7596
 7597#[gpui::test]
 7598async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7599    init_test(cx, |_| {});
 7600    let mut cx = EditorTestContext::new(cx).await;
 7601
 7602    #[track_caller]
 7603    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7604        cx.set_state(initial_state);
 7605        cx.update_editor(|e, window, cx| {
 7606            e.split_selection_into_lines(&Default::default(), window, cx)
 7607        });
 7608        cx.assert_editor_state(expected_state);
 7609    }
 7610
 7611    // Selection starts and ends at the middle of lines, left-to-right
 7612    test(
 7613        &mut cx,
 7614        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7615        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7616    );
 7617    // Same thing, right-to-left
 7618    test(
 7619        &mut cx,
 7620        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7621        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7622    );
 7623
 7624    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7625    test(
 7626        &mut cx,
 7627        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7628        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7629    );
 7630    // Same thing, right-to-left
 7631    test(
 7632        &mut cx,
 7633        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7634        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7635    );
 7636
 7637    // Whole buffer, left-to-right, last line ends with newline
 7638    test(
 7639        &mut cx,
 7640        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7641        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7642    );
 7643    // Same thing, right-to-left
 7644    test(
 7645        &mut cx,
 7646        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7647        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7648    );
 7649
 7650    // Starts at the end of a line, ends at the start of another
 7651    test(
 7652        &mut cx,
 7653        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7654        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7655    );
 7656}
 7657
 7658#[gpui::test]
 7659async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7660    init_test(cx, |_| {});
 7661
 7662    let editor = cx.add_window(|window, cx| {
 7663        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7664        build_editor(buffer, window, cx)
 7665    });
 7666
 7667    // setup
 7668    _ = editor.update(cx, |editor, window, cx| {
 7669        editor.fold_creases(
 7670            vec![
 7671                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7672                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7673                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7674            ],
 7675            true,
 7676            window,
 7677            cx,
 7678        );
 7679        assert_eq!(
 7680            editor.display_text(cx),
 7681            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7682        );
 7683    });
 7684
 7685    _ = editor.update(cx, |editor, window, cx| {
 7686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7687            s.select_display_ranges([
 7688                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7689                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7691                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7692            ])
 7693        });
 7694        editor.split_selection_into_lines(&Default::default(), window, cx);
 7695        assert_eq!(
 7696            editor.display_text(cx),
 7697            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7698        );
 7699    });
 7700    EditorTestContext::for_editor(editor, cx)
 7701        .await
 7702        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7703
 7704    _ = editor.update(cx, |editor, window, cx| {
 7705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7706            s.select_display_ranges([
 7707                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7708            ])
 7709        });
 7710        editor.split_selection_into_lines(&Default::default(), window, cx);
 7711        assert_eq!(
 7712            editor.display_text(cx),
 7713            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7714        );
 7715        assert_eq!(
 7716            editor.selections.display_ranges(cx),
 7717            [
 7718                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7719                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7720                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7721                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7722                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7723                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7724                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7725            ]
 7726        );
 7727    });
 7728    EditorTestContext::for_editor(editor, cx)
 7729        .await
 7730        .assert_editor_state(
 7731            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7732        );
 7733}
 7734
 7735#[gpui::test]
 7736async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7737    init_test(cx, |_| {});
 7738
 7739    let mut cx = EditorTestContext::new(cx).await;
 7740
 7741    cx.set_state(indoc!(
 7742        r#"abc
 7743           defˇghi
 7744
 7745           jk
 7746           nlmo
 7747           "#
 7748    ));
 7749
 7750    cx.update_editor(|editor, window, cx| {
 7751        editor.add_selection_above(&Default::default(), window, cx);
 7752    });
 7753
 7754    cx.assert_editor_state(indoc!(
 7755        r#"abcˇ
 7756           defˇghi
 7757
 7758           jk
 7759           nlmo
 7760           "#
 7761    ));
 7762
 7763    cx.update_editor(|editor, window, cx| {
 7764        editor.add_selection_above(&Default::default(), window, cx);
 7765    });
 7766
 7767    cx.assert_editor_state(indoc!(
 7768        r#"abcˇ
 7769            defˇghi
 7770
 7771            jk
 7772            nlmo
 7773            "#
 7774    ));
 7775
 7776    cx.update_editor(|editor, window, cx| {
 7777        editor.add_selection_below(&Default::default(), window, cx);
 7778    });
 7779
 7780    cx.assert_editor_state(indoc!(
 7781        r#"abc
 7782           defˇghi
 7783
 7784           jk
 7785           nlmo
 7786           "#
 7787    ));
 7788
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.undo_selection(&Default::default(), window, cx);
 7791    });
 7792
 7793    cx.assert_editor_state(indoc!(
 7794        r#"abcˇ
 7795           defˇghi
 7796
 7797           jk
 7798           nlmo
 7799           "#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.redo_selection(&Default::default(), window, cx);
 7804    });
 7805
 7806    cx.assert_editor_state(indoc!(
 7807        r#"abc
 7808           defˇghi
 7809
 7810           jk
 7811           nlmo
 7812           "#
 7813    ));
 7814
 7815    cx.update_editor(|editor, window, cx| {
 7816        editor.add_selection_below(&Default::default(), window, cx);
 7817    });
 7818
 7819    cx.assert_editor_state(indoc!(
 7820        r#"abc
 7821           defˇghi
 7822           ˇ
 7823           jk
 7824           nlmo
 7825           "#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    cx.assert_editor_state(indoc!(
 7833        r#"abc
 7834           defˇghi
 7835           ˇ
 7836           jkˇ
 7837           nlmo
 7838           "#
 7839    ));
 7840
 7841    cx.update_editor(|editor, window, cx| {
 7842        editor.add_selection_below(&Default::default(), window, cx);
 7843    });
 7844
 7845    cx.assert_editor_state(indoc!(
 7846        r#"abc
 7847           defˇghi
 7848           ˇ
 7849           jkˇ
 7850           nlmˇo
 7851           "#
 7852    ));
 7853
 7854    cx.update_editor(|editor, window, cx| {
 7855        editor.add_selection_below(&Default::default(), window, cx);
 7856    });
 7857
 7858    cx.assert_editor_state(indoc!(
 7859        r#"abc
 7860           defˇghi
 7861           ˇ
 7862           jkˇ
 7863           nlmˇo
 7864           ˇ"#
 7865    ));
 7866
 7867    // change selections
 7868    cx.set_state(indoc!(
 7869        r#"abc
 7870           def«ˇg»hi
 7871
 7872           jk
 7873           nlmo
 7874           "#
 7875    ));
 7876
 7877    cx.update_editor(|editor, window, cx| {
 7878        editor.add_selection_below(&Default::default(), window, cx);
 7879    });
 7880
 7881    cx.assert_editor_state(indoc!(
 7882        r#"abc
 7883           def«ˇg»hi
 7884
 7885           jk
 7886           nlm«ˇo»
 7887           "#
 7888    ));
 7889
 7890    cx.update_editor(|editor, window, cx| {
 7891        editor.add_selection_below(&Default::default(), window, cx);
 7892    });
 7893
 7894    cx.assert_editor_state(indoc!(
 7895        r#"abc
 7896           def«ˇg»hi
 7897
 7898           jk
 7899           nlm«ˇo»
 7900           "#
 7901    ));
 7902
 7903    cx.update_editor(|editor, window, cx| {
 7904        editor.add_selection_above(&Default::default(), window, cx);
 7905    });
 7906
 7907    cx.assert_editor_state(indoc!(
 7908        r#"abc
 7909           def«ˇg»hi
 7910
 7911           jk
 7912           nlmo
 7913           "#
 7914    ));
 7915
 7916    cx.update_editor(|editor, window, cx| {
 7917        editor.add_selection_above(&Default::default(), window, cx);
 7918    });
 7919
 7920    cx.assert_editor_state(indoc!(
 7921        r#"abc
 7922           def«ˇg»hi
 7923
 7924           jk
 7925           nlmo
 7926           "#
 7927    ));
 7928
 7929    // Change selections again
 7930    cx.set_state(indoc!(
 7931        r#"a«bc
 7932           defgˇ»hi
 7933
 7934           jk
 7935           nlmo
 7936           "#
 7937    ));
 7938
 7939    cx.update_editor(|editor, window, cx| {
 7940        editor.add_selection_below(&Default::default(), window, cx);
 7941    });
 7942
 7943    cx.assert_editor_state(indoc!(
 7944        r#"a«bcˇ»
 7945           d«efgˇ»hi
 7946
 7947           j«kˇ»
 7948           nlmo
 7949           "#
 7950    ));
 7951
 7952    cx.update_editor(|editor, window, cx| {
 7953        editor.add_selection_below(&Default::default(), window, cx);
 7954    });
 7955    cx.assert_editor_state(indoc!(
 7956        r#"a«bcˇ»
 7957           d«efgˇ»hi
 7958
 7959           j«kˇ»
 7960           n«lmoˇ»
 7961           "#
 7962    ));
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.add_selection_above(&Default::default(), window, cx);
 7965    });
 7966
 7967    cx.assert_editor_state(indoc!(
 7968        r#"a«bcˇ»
 7969           d«efgˇ»hi
 7970
 7971           j«kˇ»
 7972           nlmo
 7973           "#
 7974    ));
 7975
 7976    // Change selections again
 7977    cx.set_state(indoc!(
 7978        r#"abc
 7979           d«ˇefghi
 7980
 7981           jk
 7982           nlm»o
 7983           "#
 7984    ));
 7985
 7986    cx.update_editor(|editor, window, cx| {
 7987        editor.add_selection_above(&Default::default(), window, cx);
 7988    });
 7989
 7990    cx.assert_editor_state(indoc!(
 7991        r#"a«ˇbc»
 7992           d«ˇef»ghi
 7993
 7994           j«ˇk»
 7995           n«ˇlm»o
 7996           "#
 7997    ));
 7998
 7999    cx.update_editor(|editor, window, cx| {
 8000        editor.add_selection_below(&Default::default(), window, cx);
 8001    });
 8002
 8003    cx.assert_editor_state(indoc!(
 8004        r#"abc
 8005           d«ˇef»ghi
 8006
 8007           j«ˇk»
 8008           n«ˇlm»o
 8009           "#
 8010    ));
 8011}
 8012
 8013#[gpui::test]
 8014async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8015    init_test(cx, |_| {});
 8016    let mut cx = EditorTestContext::new(cx).await;
 8017
 8018    cx.set_state(indoc!(
 8019        r#"line onˇe
 8020           liˇne two
 8021           line three
 8022           line four"#
 8023    ));
 8024
 8025    cx.update_editor(|editor, window, cx| {
 8026        editor.add_selection_below(&Default::default(), window, cx);
 8027    });
 8028
 8029    // test multiple cursors expand in the same direction
 8030    cx.assert_editor_state(indoc!(
 8031        r#"line onˇe
 8032           liˇne twˇo
 8033           liˇne three
 8034           line four"#
 8035    ));
 8036
 8037    cx.update_editor(|editor, window, cx| {
 8038        editor.add_selection_below(&Default::default(), window, cx);
 8039    });
 8040
 8041    cx.update_editor(|editor, window, cx| {
 8042        editor.add_selection_below(&Default::default(), window, cx);
 8043    });
 8044
 8045    // test multiple cursors expand below overflow
 8046    cx.assert_editor_state(indoc!(
 8047        r#"line onˇe
 8048           liˇne twˇo
 8049           liˇne thˇree
 8050           liˇne foˇur"#
 8051    ));
 8052
 8053    cx.update_editor(|editor, window, cx| {
 8054        editor.add_selection_above(&Default::default(), window, cx);
 8055    });
 8056
 8057    // test multiple cursors retrieves back correctly
 8058    cx.assert_editor_state(indoc!(
 8059        r#"line onˇe
 8060           liˇne twˇo
 8061           liˇne thˇree
 8062           line four"#
 8063    ));
 8064
 8065    cx.update_editor(|editor, window, cx| {
 8066        editor.add_selection_above(&Default::default(), window, cx);
 8067    });
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.add_selection_above(&Default::default(), window, cx);
 8071    });
 8072
 8073    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8074    cx.assert_editor_state(indoc!(
 8075        r#"liˇne onˇe
 8076           liˇne two
 8077           line three
 8078           line four"#
 8079    ));
 8080
 8081    cx.update_editor(|editor, window, cx| {
 8082        editor.undo_selection(&Default::default(), window, cx);
 8083    });
 8084
 8085    // test undo
 8086    cx.assert_editor_state(indoc!(
 8087        r#"line onˇe
 8088           liˇne twˇo
 8089           line three
 8090           line four"#
 8091    ));
 8092
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.redo_selection(&Default::default(), window, cx);
 8095    });
 8096
 8097    // test redo
 8098    cx.assert_editor_state(indoc!(
 8099        r#"liˇne onˇe
 8100           liˇne two
 8101           line three
 8102           line four"#
 8103    ));
 8104
 8105    cx.set_state(indoc!(
 8106        r#"abcd
 8107           ef«ghˇ»
 8108           ijkl
 8109           «mˇ»nop"#
 8110    ));
 8111
 8112    cx.update_editor(|editor, window, cx| {
 8113        editor.add_selection_above(&Default::default(), window, cx);
 8114    });
 8115
 8116    // test multiple selections expand in the same direction
 8117    cx.assert_editor_state(indoc!(
 8118        r#"ab«cdˇ»
 8119           ef«ghˇ»
 8120           «iˇ»jkl
 8121           «mˇ»nop"#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_above(&Default::default(), window, cx);
 8126    });
 8127
 8128    // test multiple selection upward overflow
 8129    cx.assert_editor_state(indoc!(
 8130        r#"ab«cdˇ»
 8131           «eˇ»f«ghˇ»
 8132           «iˇ»jkl
 8133           «mˇ»nop"#
 8134    ));
 8135
 8136    cx.update_editor(|editor, window, cx| {
 8137        editor.add_selection_below(&Default::default(), window, cx);
 8138    });
 8139
 8140    // test multiple selection retrieves back correctly
 8141    cx.assert_editor_state(indoc!(
 8142        r#"abcd
 8143           ef«ghˇ»
 8144           «iˇ»jkl
 8145           «mˇ»nop"#
 8146    ));
 8147
 8148    cx.update_editor(|editor, window, cx| {
 8149        editor.add_selection_below(&Default::default(), window, cx);
 8150    });
 8151
 8152    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8153    cx.assert_editor_state(indoc!(
 8154        r#"abcd
 8155           ef«ghˇ»
 8156           ij«klˇ»
 8157           «mˇ»nop"#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    // test undo
 8165    cx.assert_editor_state(indoc!(
 8166        r#"abcd
 8167           ef«ghˇ»
 8168           «iˇ»jkl
 8169           «mˇ»nop"#
 8170    ));
 8171
 8172    cx.update_editor(|editor, window, cx| {
 8173        editor.redo_selection(&Default::default(), window, cx);
 8174    });
 8175
 8176    // test redo
 8177    cx.assert_editor_state(indoc!(
 8178        r#"abcd
 8179           ef«ghˇ»
 8180           ij«klˇ»
 8181           «mˇ»nop"#
 8182    ));
 8183}
 8184
 8185#[gpui::test]
 8186async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8187    init_test(cx, |_| {});
 8188    let mut cx = EditorTestContext::new(cx).await;
 8189
 8190    cx.set_state(indoc!(
 8191        r#"line onˇe
 8192           liˇne two
 8193           line three
 8194           line four"#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199        editor.add_selection_below(&Default::default(), window, cx);
 8200        editor.add_selection_below(&Default::default(), window, cx);
 8201    });
 8202
 8203    // initial state with two multi cursor groups
 8204    cx.assert_editor_state(indoc!(
 8205        r#"line onˇe
 8206           liˇne twˇo
 8207           liˇne thˇree
 8208           liˇne foˇur"#
 8209    ));
 8210
 8211    // add single cursor in middle - simulate opt click
 8212    cx.update_editor(|editor, window, cx| {
 8213        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8214        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8215        editor.end_selection(window, cx);
 8216    });
 8217
 8218    cx.assert_editor_state(indoc!(
 8219        r#"line onˇe
 8220           liˇne twˇo
 8221           liˇneˇ thˇree
 8222           liˇne foˇur"#
 8223    ));
 8224
 8225    cx.update_editor(|editor, window, cx| {
 8226        editor.add_selection_above(&Default::default(), window, cx);
 8227    });
 8228
 8229    // test new added selection expands above and existing selection shrinks
 8230    cx.assert_editor_state(indoc!(
 8231        r#"line onˇe
 8232           liˇneˇ twˇo
 8233           liˇneˇ thˇree
 8234           line four"#
 8235    ));
 8236
 8237    cx.update_editor(|editor, window, cx| {
 8238        editor.add_selection_above(&Default::default(), window, cx);
 8239    });
 8240
 8241    // test new added selection expands above and existing selection shrinks
 8242    cx.assert_editor_state(indoc!(
 8243        r#"lineˇ onˇe
 8244           liˇneˇ twˇo
 8245           lineˇ three
 8246           line four"#
 8247    ));
 8248
 8249    // intial state with two selection groups
 8250    cx.set_state(indoc!(
 8251        r#"abcd
 8252           ef«ghˇ»
 8253           ijkl
 8254           «mˇ»nop"#
 8255    ));
 8256
 8257    cx.update_editor(|editor, window, cx| {
 8258        editor.add_selection_above(&Default::default(), window, cx);
 8259        editor.add_selection_above(&Default::default(), window, cx);
 8260    });
 8261
 8262    cx.assert_editor_state(indoc!(
 8263        r#"ab«cdˇ»
 8264           «eˇ»f«ghˇ»
 8265           «iˇ»jkl
 8266           «mˇ»nop"#
 8267    ));
 8268
 8269    // add single selection in middle - simulate opt drag
 8270    cx.update_editor(|editor, window, cx| {
 8271        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8272        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8273        editor.update_selection(
 8274            DisplayPoint::new(DisplayRow(2), 4),
 8275            0,
 8276            gpui::Point::<f32>::default(),
 8277            window,
 8278            cx,
 8279        );
 8280        editor.end_selection(window, cx);
 8281    });
 8282
 8283    cx.assert_editor_state(indoc!(
 8284        r#"ab«cdˇ»
 8285           «eˇ»f«ghˇ»
 8286           «iˇ»jk«lˇ»
 8287           «mˇ»nop"#
 8288    ));
 8289
 8290    cx.update_editor(|editor, window, cx| {
 8291        editor.add_selection_below(&Default::default(), window, cx);
 8292    });
 8293
 8294    // test new added selection expands below, others shrinks from above
 8295    cx.assert_editor_state(indoc!(
 8296        r#"abcd
 8297           ef«ghˇ»
 8298           «iˇ»jk«lˇ»
 8299           «mˇ»no«pˇ»"#
 8300    ));
 8301}
 8302
 8303#[gpui::test]
 8304async fn test_select_next(cx: &mut TestAppContext) {
 8305    init_test(cx, |_| {});
 8306
 8307    let mut cx = EditorTestContext::new(cx).await;
 8308    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8309
 8310    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8311        .unwrap();
 8312    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8313
 8314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8315        .unwrap();
 8316    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8317
 8318    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8319    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8320
 8321    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8322    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8323
 8324    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8325        .unwrap();
 8326    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8327
 8328    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8329        .unwrap();
 8330    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8331
 8332    // Test selection direction should be preserved
 8333    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8334
 8335    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8336        .unwrap();
 8337    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8338}
 8339
 8340#[gpui::test]
 8341async fn test_select_all_matches(cx: &mut TestAppContext) {
 8342    init_test(cx, |_| {});
 8343
 8344    let mut cx = EditorTestContext::new(cx).await;
 8345
 8346    // Test caret-only selections
 8347    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8348    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8349        .unwrap();
 8350    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8351
 8352    // Test left-to-right selections
 8353    cx.set_state("abc\n«abcˇ»\nabc");
 8354    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8355        .unwrap();
 8356    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8357
 8358    // Test right-to-left selections
 8359    cx.set_state("abc\n«ˇabc»\nabc");
 8360    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8361        .unwrap();
 8362    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8363
 8364    // Test selecting whitespace with caret selection
 8365    cx.set_state("abc\nˇ   abc\nabc");
 8366    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8367        .unwrap();
 8368    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8369
 8370    // Test selecting whitespace with left-to-right selection
 8371    cx.set_state("abc\n«ˇ  »abc\nabc");
 8372    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8373        .unwrap();
 8374    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8375
 8376    // Test no matches with right-to-left selection
 8377    cx.set_state("abc\n«  ˇ»abc\nabc");
 8378    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8379        .unwrap();
 8380    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8381
 8382    // Test with a single word and clip_at_line_ends=true (#29823)
 8383    cx.set_state("aˇbc");
 8384    cx.update_editor(|e, window, cx| {
 8385        e.set_clip_at_line_ends(true, cx);
 8386        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8387        e.set_clip_at_line_ends(false, cx);
 8388    });
 8389    cx.assert_editor_state("«abcˇ»");
 8390}
 8391
 8392#[gpui::test]
 8393async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8394    init_test(cx, |_| {});
 8395
 8396    let mut cx = EditorTestContext::new(cx).await;
 8397
 8398    let large_body_1 = "\nd".repeat(200);
 8399    let large_body_2 = "\ne".repeat(200);
 8400
 8401    cx.set_state(&format!(
 8402        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8403    ));
 8404    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8405        let scroll_position = editor.scroll_position(cx);
 8406        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8407        scroll_position
 8408    });
 8409
 8410    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8411        .unwrap();
 8412    cx.assert_editor_state(&format!(
 8413        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8414    ));
 8415    let scroll_position_after_selection =
 8416        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8417    assert_eq!(
 8418        initial_scroll_position, scroll_position_after_selection,
 8419        "Scroll position should not change after selecting all matches"
 8420    );
 8421}
 8422
 8423#[gpui::test]
 8424async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8425    init_test(cx, |_| {});
 8426
 8427    let mut cx = EditorLspTestContext::new_rust(
 8428        lsp::ServerCapabilities {
 8429            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8430            ..Default::default()
 8431        },
 8432        cx,
 8433    )
 8434    .await;
 8435
 8436    cx.set_state(indoc! {"
 8437        line 1
 8438        line 2
 8439        linˇe 3
 8440        line 4
 8441        line 5
 8442    "});
 8443
 8444    // Make an edit
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.handle_input("X", window, cx);
 8447    });
 8448
 8449    // Move cursor to a different position
 8450    cx.update_editor(|editor, window, cx| {
 8451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8452            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8453        });
 8454    });
 8455
 8456    cx.assert_editor_state(indoc! {"
 8457        line 1
 8458        line 2
 8459        linXe 3
 8460        line 4
 8461        liˇne 5
 8462    "});
 8463
 8464    cx.lsp
 8465        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8466            Ok(Some(vec![lsp::TextEdit::new(
 8467                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8468                "PREFIX ".to_string(),
 8469            )]))
 8470        });
 8471
 8472    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8473        .unwrap()
 8474        .await
 8475        .unwrap();
 8476
 8477    cx.assert_editor_state(indoc! {"
 8478        PREFIX line 1
 8479        line 2
 8480        linXe 3
 8481        line 4
 8482        liˇne 5
 8483    "});
 8484
 8485    // Undo formatting
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.undo(&Default::default(), window, cx);
 8488    });
 8489
 8490    // Verify cursor moved back to position after edit
 8491    cx.assert_editor_state(indoc! {"
 8492        line 1
 8493        line 2
 8494        linXˇe 3
 8495        line 4
 8496        line 5
 8497    "});
 8498}
 8499
 8500#[gpui::test]
 8501async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8502    init_test(cx, |_| {});
 8503
 8504    let mut cx = EditorTestContext::new(cx).await;
 8505
 8506    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8507    cx.update_editor(|editor, window, cx| {
 8508        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8509    });
 8510
 8511    cx.set_state(indoc! {"
 8512        line 1
 8513        line 2
 8514        linˇe 3
 8515        line 4
 8516        line 5
 8517        line 6
 8518        line 7
 8519        line 8
 8520        line 9
 8521        line 10
 8522    "});
 8523
 8524    let snapshot = cx.buffer_snapshot();
 8525    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8526
 8527    cx.update(|_, cx| {
 8528        provider.update(cx, |provider, _| {
 8529            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8530                id: None,
 8531                edits: vec![(edit_position..edit_position, "X".into())],
 8532                edit_preview: None,
 8533            }))
 8534        })
 8535    });
 8536
 8537    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8538    cx.update_editor(|editor, window, cx| {
 8539        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8540    });
 8541
 8542    cx.assert_editor_state(indoc! {"
 8543        line 1
 8544        line 2
 8545        lineXˇ 3
 8546        line 4
 8547        line 5
 8548        line 6
 8549        line 7
 8550        line 8
 8551        line 9
 8552        line 10
 8553    "});
 8554
 8555    cx.update_editor(|editor, window, cx| {
 8556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8557            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8558        });
 8559    });
 8560
 8561    cx.assert_editor_state(indoc! {"
 8562        line 1
 8563        line 2
 8564        lineX 3
 8565        line 4
 8566        line 5
 8567        line 6
 8568        line 7
 8569        line 8
 8570        line 9
 8571        liˇne 10
 8572    "});
 8573
 8574    cx.update_editor(|editor, window, cx| {
 8575        editor.undo(&Default::default(), window, cx);
 8576    });
 8577
 8578    cx.assert_editor_state(indoc! {"
 8579        line 1
 8580        line 2
 8581        lineˇ 3
 8582        line 4
 8583        line 5
 8584        line 6
 8585        line 7
 8586        line 8
 8587        line 9
 8588        line 10
 8589    "});
 8590}
 8591
 8592#[gpui::test]
 8593async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8594    init_test(cx, |_| {});
 8595
 8596    let mut cx = EditorTestContext::new(cx).await;
 8597    cx.set_state(
 8598        r#"let foo = 2;
 8599lˇet foo = 2;
 8600let fooˇ = 2;
 8601let foo = 2;
 8602let foo = ˇ2;"#,
 8603    );
 8604
 8605    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8606        .unwrap();
 8607    cx.assert_editor_state(
 8608        r#"let foo = 2;
 8609«letˇ» foo = 2;
 8610let «fooˇ» = 2;
 8611let foo = 2;
 8612let foo = «2ˇ»;"#,
 8613    );
 8614
 8615    // noop for multiple selections with different contents
 8616    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8617        .unwrap();
 8618    cx.assert_editor_state(
 8619        r#"let foo = 2;
 8620«letˇ» foo = 2;
 8621let «fooˇ» = 2;
 8622let foo = 2;
 8623let foo = «2ˇ»;"#,
 8624    );
 8625
 8626    // Test last selection direction should be preserved
 8627    cx.set_state(
 8628        r#"let foo = 2;
 8629let foo = 2;
 8630let «fooˇ» = 2;
 8631let «ˇfoo» = 2;
 8632let foo = 2;"#,
 8633    );
 8634
 8635    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8636        .unwrap();
 8637    cx.assert_editor_state(
 8638        r#"let foo = 2;
 8639let foo = 2;
 8640let «fooˇ» = 2;
 8641let «ˇfoo» = 2;
 8642let «ˇfoo» = 2;"#,
 8643    );
 8644}
 8645
 8646#[gpui::test]
 8647async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8648    init_test(cx, |_| {});
 8649
 8650    let mut cx =
 8651        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8652
 8653    cx.assert_editor_state(indoc! {"
 8654        ˇbbb
 8655        ccc
 8656
 8657        bbb
 8658        ccc
 8659        "});
 8660    cx.dispatch_action(SelectPrevious::default());
 8661    cx.assert_editor_state(indoc! {"
 8662                «bbbˇ»
 8663                ccc
 8664
 8665                bbb
 8666                ccc
 8667                "});
 8668    cx.dispatch_action(SelectPrevious::default());
 8669    cx.assert_editor_state(indoc! {"
 8670                «bbbˇ»
 8671                ccc
 8672
 8673                «bbbˇ»
 8674                ccc
 8675                "});
 8676}
 8677
 8678#[gpui::test]
 8679async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8680    init_test(cx, |_| {});
 8681
 8682    let mut cx = EditorTestContext::new(cx).await;
 8683    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8684
 8685    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8686        .unwrap();
 8687    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8688
 8689    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8690        .unwrap();
 8691    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8692
 8693    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8694    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8695
 8696    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8697    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8698
 8699    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8700        .unwrap();
 8701    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8702
 8703    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8704        .unwrap();
 8705    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8706}
 8707
 8708#[gpui::test]
 8709async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8710    init_test(cx, |_| {});
 8711
 8712    let mut cx = EditorTestContext::new(cx).await;
 8713    cx.set_state("");
 8714
 8715    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8716        .unwrap();
 8717    cx.assert_editor_state("«aˇ»");
 8718    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8719        .unwrap();
 8720    cx.assert_editor_state("«aˇ»");
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728    cx.set_state(
 8729        r#"let foo = 2;
 8730lˇet foo = 2;
 8731let fooˇ = 2;
 8732let foo = 2;
 8733let foo = ˇ2;"#,
 8734    );
 8735
 8736    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8737        .unwrap();
 8738    cx.assert_editor_state(
 8739        r#"let foo = 2;
 8740«letˇ» foo = 2;
 8741let «fooˇ» = 2;
 8742let foo = 2;
 8743let foo = «2ˇ»;"#,
 8744    );
 8745
 8746    // noop for multiple selections with different contents
 8747    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8748        .unwrap();
 8749    cx.assert_editor_state(
 8750        r#"let foo = 2;
 8751«letˇ» foo = 2;
 8752let «fooˇ» = 2;
 8753let foo = 2;
 8754let foo = «2ˇ»;"#,
 8755    );
 8756}
 8757
 8758#[gpui::test]
 8759async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8760    init_test(cx, |_| {});
 8761
 8762    let mut cx = EditorTestContext::new(cx).await;
 8763    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8764
 8765    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8766        .unwrap();
 8767    // selection direction is preserved
 8768    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8773
 8774    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8775    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8776
 8777    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8778    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8783
 8784    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8785        .unwrap();
 8786    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8787}
 8788
 8789#[gpui::test]
 8790async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8791    init_test(cx, |_| {});
 8792
 8793    let language = Arc::new(Language::new(
 8794        LanguageConfig::default(),
 8795        Some(tree_sitter_rust::LANGUAGE.into()),
 8796    ));
 8797
 8798    let text = r#"
 8799        use mod1::mod2::{mod3, mod4};
 8800
 8801        fn fn_1(param1: bool, param2: &str) {
 8802            let var1 = "text";
 8803        }
 8804    "#
 8805    .unindent();
 8806
 8807    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8808    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8809    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8810
 8811    editor
 8812        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8813        .await;
 8814
 8815    editor.update_in(cx, |editor, window, cx| {
 8816        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8817            s.select_display_ranges([
 8818                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8819                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8820                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8821            ]);
 8822        });
 8823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8824    });
 8825    editor.update(cx, |editor, cx| {
 8826        assert_text_with_selections(
 8827            editor,
 8828            indoc! {r#"
 8829                use mod1::mod2::{mod3, «mod4ˇ»};
 8830
 8831                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8832                    let var1 = "«ˇtext»";
 8833                }
 8834            "#},
 8835            cx,
 8836        );
 8837    });
 8838
 8839    editor.update_in(cx, |editor, window, cx| {
 8840        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8841    });
 8842    editor.update(cx, |editor, cx| {
 8843        assert_text_with_selections(
 8844            editor,
 8845            indoc! {r#"
 8846                use mod1::mod2::«{mod3, mod4}ˇ»;
 8847
 8848                «ˇfn fn_1(param1: bool, param2: &str) {
 8849                    let var1 = "text";
 8850 8851            "#},
 8852            cx,
 8853        );
 8854    });
 8855
 8856    editor.update_in(cx, |editor, window, cx| {
 8857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8858    });
 8859    assert_eq!(
 8860        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8861        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8862    );
 8863
 8864    // Trying to expand the selected syntax node one more time has no effect.
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    assert_eq!(
 8869        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8870        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8871    );
 8872
 8873    editor.update_in(cx, |editor, window, cx| {
 8874        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8875    });
 8876    editor.update(cx, |editor, cx| {
 8877        assert_text_with_selections(
 8878            editor,
 8879            indoc! {r#"
 8880                use mod1::mod2::«{mod3, mod4}ˇ»;
 8881
 8882                «ˇfn fn_1(param1: bool, param2: &str) {
 8883                    let var1 = "text";
 8884 8885            "#},
 8886            cx,
 8887        );
 8888    });
 8889
 8890    editor.update_in(cx, |editor, window, cx| {
 8891        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8892    });
 8893    editor.update(cx, |editor, cx| {
 8894        assert_text_with_selections(
 8895            editor,
 8896            indoc! {r#"
 8897                use mod1::mod2::{mod3, «mod4ˇ»};
 8898
 8899                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8900                    let var1 = "«ˇtext»";
 8901                }
 8902            "#},
 8903            cx,
 8904        );
 8905    });
 8906
 8907    editor.update_in(cx, |editor, window, cx| {
 8908        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8909    });
 8910    editor.update(cx, |editor, cx| {
 8911        assert_text_with_selections(
 8912            editor,
 8913            indoc! {r#"
 8914                use mod1::mod2::{mod3, moˇd4};
 8915
 8916                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8917                    let var1 = "teˇxt";
 8918                }
 8919            "#},
 8920            cx,
 8921        );
 8922    });
 8923
 8924    // Trying to shrink the selected syntax node one more time has no effect.
 8925    editor.update_in(cx, |editor, window, cx| {
 8926        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8927    });
 8928    editor.update_in(cx, |editor, _, cx| {
 8929        assert_text_with_selections(
 8930            editor,
 8931            indoc! {r#"
 8932                use mod1::mod2::{mod3, moˇd4};
 8933
 8934                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8935                    let var1 = "teˇxt";
 8936                }
 8937            "#},
 8938            cx,
 8939        );
 8940    });
 8941
 8942    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8943    // a fold.
 8944    editor.update_in(cx, |editor, window, cx| {
 8945        editor.fold_creases(
 8946            vec![
 8947                Crease::simple(
 8948                    Point::new(0, 21)..Point::new(0, 24),
 8949                    FoldPlaceholder::test(),
 8950                ),
 8951                Crease::simple(
 8952                    Point::new(3, 20)..Point::new(3, 22),
 8953                    FoldPlaceholder::test(),
 8954                ),
 8955            ],
 8956            true,
 8957            window,
 8958            cx,
 8959        );
 8960        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8961    });
 8962    editor.update(cx, |editor, cx| {
 8963        assert_text_with_selections(
 8964            editor,
 8965            indoc! {r#"
 8966                use mod1::mod2::«{mod3, mod4}ˇ»;
 8967
 8968                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8969                    let var1 = "«ˇtext»";
 8970                }
 8971            "#},
 8972            cx,
 8973        );
 8974    });
 8975}
 8976
 8977#[gpui::test]
 8978async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8979    init_test(cx, |_| {});
 8980
 8981    let language = Arc::new(Language::new(
 8982        LanguageConfig::default(),
 8983        Some(tree_sitter_rust::LANGUAGE.into()),
 8984    ));
 8985
 8986    let text = "let a = 2;";
 8987
 8988    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8989    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8990    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8991
 8992    editor
 8993        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8994        .await;
 8995
 8996    // Test case 1: Cursor at end of word
 8997    editor.update_in(cx, |editor, window, cx| {
 8998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8999            s.select_display_ranges([
 9000                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9001            ]);
 9002        });
 9003    });
 9004    editor.update(cx, |editor, cx| {
 9005        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9006    });
 9007    editor.update_in(cx, |editor, window, cx| {
 9008        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9009    });
 9010    editor.update(cx, |editor, cx| {
 9011        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9012    });
 9013    editor.update_in(cx, |editor, window, cx| {
 9014        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9015    });
 9016    editor.update(cx, |editor, cx| {
 9017        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9018    });
 9019
 9020    // Test case 2: Cursor at end of statement
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9023            s.select_display_ranges([
 9024                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9025            ]);
 9026        });
 9027    });
 9028    editor.update(cx, |editor, cx| {
 9029        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9030    });
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9033    });
 9034    editor.update(cx, |editor, cx| {
 9035        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9036    });
 9037}
 9038
 9039#[gpui::test]
 9040async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9041    init_test(cx, |_| {});
 9042
 9043    let language = Arc::new(Language::new(
 9044        LanguageConfig {
 9045            name: "JavaScript".into(),
 9046            ..Default::default()
 9047        },
 9048        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9049    ));
 9050
 9051    let text = r#"
 9052        let a = {
 9053            key: "value",
 9054        };
 9055    "#
 9056    .unindent();
 9057
 9058    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9059    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9061
 9062    editor
 9063        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9064        .await;
 9065
 9066    // Test case 1: Cursor after '{'
 9067    editor.update_in(cx, |editor, window, cx| {
 9068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9069            s.select_display_ranges([
 9070                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9071            ]);
 9072        });
 9073    });
 9074    editor.update(cx, |editor, cx| {
 9075        assert_text_with_selections(
 9076            editor,
 9077            indoc! {r#"
 9078                let a = {ˇ
 9079                    key: "value",
 9080                };
 9081            "#},
 9082            cx,
 9083        );
 9084    });
 9085    editor.update_in(cx, |editor, window, cx| {
 9086        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9087    });
 9088    editor.update(cx, |editor, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                let a = «ˇ{
 9093                    key: "value",
 9094                }»;
 9095            "#},
 9096            cx,
 9097        );
 9098    });
 9099
 9100    // Test case 2: Cursor after ':'
 9101    editor.update_in(cx, |editor, window, cx| {
 9102        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9103            s.select_display_ranges([
 9104                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9105            ]);
 9106        });
 9107    });
 9108    editor.update(cx, |editor, cx| {
 9109        assert_text_with_selections(
 9110            editor,
 9111            indoc! {r#"
 9112                let a = {
 9113                    key:ˇ "value",
 9114                };
 9115            "#},
 9116            cx,
 9117        );
 9118    });
 9119    editor.update_in(cx, |editor, window, cx| {
 9120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9121    });
 9122    editor.update(cx, |editor, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                let a = {
 9127                    «ˇkey: "value"»,
 9128                };
 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133    editor.update_in(cx, |editor, window, cx| {
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135    });
 9136    editor.update(cx, |editor, cx| {
 9137        assert_text_with_selections(
 9138            editor,
 9139            indoc! {r#"
 9140                let a = «ˇ{
 9141                    key: "value",
 9142                }»;
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147
 9148    // Test case 3: Cursor after ','
 9149    editor.update_in(cx, |editor, window, cx| {
 9150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9151            s.select_display_ranges([
 9152                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9153            ]);
 9154        });
 9155    });
 9156    editor.update(cx, |editor, cx| {
 9157        assert_text_with_selections(
 9158            editor,
 9159            indoc! {r#"
 9160                let a = {
 9161                    key: "value",ˇ
 9162                };
 9163            "#},
 9164            cx,
 9165        );
 9166    });
 9167    editor.update_in(cx, |editor, window, cx| {
 9168        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9169    });
 9170    editor.update(cx, |editor, cx| {
 9171        assert_text_with_selections(
 9172            editor,
 9173            indoc! {r#"
 9174                let a = «ˇ{
 9175                    key: "value",
 9176                }»;
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181
 9182    // Test case 4: Cursor after ';'
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9185            s.select_display_ranges([
 9186                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9187            ]);
 9188        });
 9189    });
 9190    editor.update(cx, |editor, cx| {
 9191        assert_text_with_selections(
 9192            editor,
 9193            indoc! {r#"
 9194                let a = {
 9195                    key: "value",
 9196                };ˇ
 9197            "#},
 9198            cx,
 9199        );
 9200    });
 9201    editor.update_in(cx, |editor, window, cx| {
 9202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                «ˇlet a = {
 9209                    key: "value",
 9210                };
 9211                »"#},
 9212            cx,
 9213        );
 9214    });
 9215}
 9216
 9217#[gpui::test]
 9218async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9219    init_test(cx, |_| {});
 9220
 9221    let language = Arc::new(Language::new(
 9222        LanguageConfig::default(),
 9223        Some(tree_sitter_rust::LANGUAGE.into()),
 9224    ));
 9225
 9226    let text = r#"
 9227        use mod1::mod2::{mod3, mod4};
 9228
 9229        fn fn_1(param1: bool, param2: &str) {
 9230            let var1 = "hello world";
 9231        }
 9232    "#
 9233    .unindent();
 9234
 9235    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9236    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9237    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9238
 9239    editor
 9240        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9241        .await;
 9242
 9243    // Test 1: Cursor on a letter of a string word
 9244    editor.update_in(cx, |editor, window, cx| {
 9245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9246            s.select_display_ranges([
 9247                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9248            ]);
 9249        });
 9250    });
 9251    editor.update_in(cx, |editor, window, cx| {
 9252        assert_text_with_selections(
 9253            editor,
 9254            indoc! {r#"
 9255                use mod1::mod2::{mod3, mod4};
 9256
 9257                fn fn_1(param1: bool, param2: &str) {
 9258                    let var1 = "hˇello world";
 9259                }
 9260            "#},
 9261            cx,
 9262        );
 9263        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9264        assert_text_with_selections(
 9265            editor,
 9266            indoc! {r#"
 9267                use mod1::mod2::{mod3, mod4};
 9268
 9269                fn fn_1(param1: bool, param2: &str) {
 9270                    let var1 = "«ˇhello» world";
 9271                }
 9272            "#},
 9273            cx,
 9274        );
 9275    });
 9276
 9277    // Test 2: Partial selection within a word
 9278    editor.update_in(cx, |editor, window, cx| {
 9279        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9280            s.select_display_ranges([
 9281                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9282            ]);
 9283        });
 9284    });
 9285    editor.update_in(cx, |editor, window, cx| {
 9286        assert_text_with_selections(
 9287            editor,
 9288            indoc! {r#"
 9289                use mod1::mod2::{mod3, mod4};
 9290
 9291                fn fn_1(param1: bool, param2: &str) {
 9292                    let var1 = "h«elˇ»lo world";
 9293                }
 9294            "#},
 9295            cx,
 9296        );
 9297        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9298        assert_text_with_selections(
 9299            editor,
 9300            indoc! {r#"
 9301                use mod1::mod2::{mod3, mod4};
 9302
 9303                fn fn_1(param1: bool, param2: &str) {
 9304                    let var1 = "«ˇhello» world";
 9305                }
 9306            "#},
 9307            cx,
 9308        );
 9309    });
 9310
 9311    // Test 3: Complete word already selected
 9312    editor.update_in(cx, |editor, window, cx| {
 9313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9314            s.select_display_ranges([
 9315                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9316            ]);
 9317        });
 9318    });
 9319    editor.update_in(cx, |editor, window, cx| {
 9320        assert_text_with_selections(
 9321            editor,
 9322            indoc! {r#"
 9323                use mod1::mod2::{mod3, mod4};
 9324
 9325                fn fn_1(param1: bool, param2: &str) {
 9326                    let var1 = "«helloˇ» world";
 9327                }
 9328            "#},
 9329            cx,
 9330        );
 9331        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9332        assert_text_with_selections(
 9333            editor,
 9334            indoc! {r#"
 9335                use mod1::mod2::{mod3, mod4};
 9336
 9337                fn fn_1(param1: bool, param2: &str) {
 9338                    let var1 = "«hello worldˇ»";
 9339                }
 9340            "#},
 9341            cx,
 9342        );
 9343    });
 9344
 9345    // Test 4: Selection spanning across words
 9346    editor.update_in(cx, |editor, window, cx| {
 9347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9348            s.select_display_ranges([
 9349                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9350            ]);
 9351        });
 9352    });
 9353    editor.update_in(cx, |editor, window, cx| {
 9354        assert_text_with_selections(
 9355            editor,
 9356            indoc! {r#"
 9357                use mod1::mod2::{mod3, mod4};
 9358
 9359                fn fn_1(param1: bool, param2: &str) {
 9360                    let var1 = "hel«lo woˇ»rld";
 9361                }
 9362            "#},
 9363            cx,
 9364        );
 9365        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9366        assert_text_with_selections(
 9367            editor,
 9368            indoc! {r#"
 9369                use mod1::mod2::{mod3, mod4};
 9370
 9371                fn fn_1(param1: bool, param2: &str) {
 9372                    let var1 = "«ˇhello world»";
 9373                }
 9374            "#},
 9375            cx,
 9376        );
 9377    });
 9378
 9379    // Test 5: Expansion beyond string
 9380    editor.update_in(cx, |editor, window, cx| {
 9381        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9382        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9383        assert_text_with_selections(
 9384            editor,
 9385            indoc! {r#"
 9386                use mod1::mod2::{mod3, mod4};
 9387
 9388                fn fn_1(param1: bool, param2: &str) {
 9389                    «ˇlet var1 = "hello world";»
 9390                }
 9391            "#},
 9392            cx,
 9393        );
 9394    });
 9395}
 9396
 9397#[gpui::test]
 9398async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9399    init_test(cx, |_| {});
 9400
 9401    let mut cx = EditorTestContext::new(cx).await;
 9402
 9403    let language = Arc::new(Language::new(
 9404        LanguageConfig::default(),
 9405        Some(tree_sitter_rust::LANGUAGE.into()),
 9406    ));
 9407
 9408    cx.update_buffer(|buffer, cx| {
 9409        buffer.set_language(Some(language), cx);
 9410    });
 9411
 9412    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9413    cx.update_editor(|editor, window, cx| {
 9414        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9415    });
 9416
 9417    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9418
 9419    cx.set_state(indoc! { r#"fn a() {
 9420          // what
 9421          // a
 9422          // ˇlong
 9423          // method
 9424          // I
 9425          // sure
 9426          // hope
 9427          // it
 9428          // works
 9429    }"# });
 9430
 9431    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9432    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9433    cx.update(|_, cx| {
 9434        multi_buffer.update(cx, |multi_buffer, cx| {
 9435            multi_buffer.set_excerpts_for_path(
 9436                PathKey::for_buffer(&buffer, cx),
 9437                buffer,
 9438                [Point::new(1, 0)..Point::new(1, 0)],
 9439                3,
 9440                cx,
 9441            );
 9442        });
 9443    });
 9444
 9445    let editor2 = cx.new_window_entity(|window, cx| {
 9446        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9447    });
 9448
 9449    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9450    cx.update_editor(|editor, window, cx| {
 9451        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9452            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9453        })
 9454    });
 9455
 9456    cx.assert_editor_state(indoc! { "
 9457        fn a() {
 9458              // what
 9459              // a
 9460        ˇ      // long
 9461              // method"});
 9462
 9463    cx.update_editor(|editor, window, cx| {
 9464        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9465    });
 9466
 9467    // Although we could potentially make the action work when the syntax node
 9468    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9469    // did. Maybe we could also expand the excerpt to contain the range?
 9470    cx.assert_editor_state(indoc! { "
 9471        fn a() {
 9472              // what
 9473              // a
 9474        ˇ      // long
 9475              // method"});
 9476}
 9477
 9478#[gpui::test]
 9479async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9480    init_test(cx, |_| {});
 9481
 9482    let base_text = r#"
 9483        impl A {
 9484            // this is an uncommitted comment
 9485
 9486            fn b() {
 9487                c();
 9488            }
 9489
 9490            // this is another uncommitted comment
 9491
 9492            fn d() {
 9493                // e
 9494                // f
 9495            }
 9496        }
 9497
 9498        fn g() {
 9499            // h
 9500        }
 9501    "#
 9502    .unindent();
 9503
 9504    let text = r#"
 9505        ˇimpl A {
 9506
 9507            fn b() {
 9508                c();
 9509            }
 9510
 9511            fn d() {
 9512                // e
 9513                // f
 9514            }
 9515        }
 9516
 9517        fn g() {
 9518            // h
 9519        }
 9520    "#
 9521    .unindent();
 9522
 9523    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9524    cx.set_state(&text);
 9525    cx.set_head_text(&base_text);
 9526    cx.update_editor(|editor, window, cx| {
 9527        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9528    });
 9529
 9530    cx.assert_state_with_diff(
 9531        "
 9532        ˇimpl A {
 9533      -     // this is an uncommitted comment
 9534
 9535            fn b() {
 9536                c();
 9537            }
 9538
 9539      -     // this is another uncommitted comment
 9540      -
 9541            fn d() {
 9542                // e
 9543                // f
 9544            }
 9545        }
 9546
 9547        fn g() {
 9548            // h
 9549        }
 9550    "
 9551        .unindent(),
 9552    );
 9553
 9554    let expected_display_text = "
 9555        impl A {
 9556            // this is an uncommitted comment
 9557
 9558            fn b() {
 9559 9560            }
 9561
 9562            // this is another uncommitted comment
 9563
 9564            fn d() {
 9565 9566            }
 9567        }
 9568
 9569        fn g() {
 9570 9571        }
 9572        "
 9573    .unindent();
 9574
 9575    cx.update_editor(|editor, window, cx| {
 9576        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9577        assert_eq!(editor.display_text(cx), expected_display_text);
 9578    });
 9579}
 9580
 9581#[gpui::test]
 9582async fn test_autoindent(cx: &mut TestAppContext) {
 9583    init_test(cx, |_| {});
 9584
 9585    let language = Arc::new(
 9586        Language::new(
 9587            LanguageConfig {
 9588                brackets: BracketPairConfig {
 9589                    pairs: vec![
 9590                        BracketPair {
 9591                            start: "{".to_string(),
 9592                            end: "}".to_string(),
 9593                            close: false,
 9594                            surround: false,
 9595                            newline: true,
 9596                        },
 9597                        BracketPair {
 9598                            start: "(".to_string(),
 9599                            end: ")".to_string(),
 9600                            close: false,
 9601                            surround: false,
 9602                            newline: true,
 9603                        },
 9604                    ],
 9605                    ..Default::default()
 9606                },
 9607                ..Default::default()
 9608            },
 9609            Some(tree_sitter_rust::LANGUAGE.into()),
 9610        )
 9611        .with_indents_query(
 9612            r#"
 9613                (_ "(" ")" @end) @indent
 9614                (_ "{" "}" @end) @indent
 9615            "#,
 9616        )
 9617        .unwrap(),
 9618    );
 9619
 9620    let text = "fn a() {}";
 9621
 9622    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9623    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9624    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9625    editor
 9626        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9627        .await;
 9628
 9629    editor.update_in(cx, |editor, window, cx| {
 9630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9631            s.select_ranges([5..5, 8..8, 9..9])
 9632        });
 9633        editor.newline(&Newline, window, cx);
 9634        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9635        assert_eq!(
 9636            editor.selections.ranges(&editor.display_snapshot(cx)),
 9637            &[
 9638                Point::new(1, 4)..Point::new(1, 4),
 9639                Point::new(3, 4)..Point::new(3, 4),
 9640                Point::new(5, 0)..Point::new(5, 0)
 9641            ]
 9642        );
 9643    });
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9648    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9649
 9650    let language = Arc::new(
 9651        Language::new(
 9652            LanguageConfig {
 9653                brackets: BracketPairConfig {
 9654                    pairs: vec![
 9655                        BracketPair {
 9656                            start: "{".to_string(),
 9657                            end: "}".to_string(),
 9658                            close: false,
 9659                            surround: false,
 9660                            newline: true,
 9661                        },
 9662                        BracketPair {
 9663                            start: "(".to_string(),
 9664                            end: ")".to_string(),
 9665                            close: false,
 9666                            surround: false,
 9667                            newline: true,
 9668                        },
 9669                    ],
 9670                    ..Default::default()
 9671                },
 9672                ..Default::default()
 9673            },
 9674            Some(tree_sitter_rust::LANGUAGE.into()),
 9675        )
 9676        .with_indents_query(
 9677            r#"
 9678                (_ "(" ")" @end) @indent
 9679                (_ "{" "}" @end) @indent
 9680            "#,
 9681        )
 9682        .unwrap(),
 9683    );
 9684
 9685    let text = "fn a() {}";
 9686
 9687    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9688    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9689    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9690    editor
 9691        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9692        .await;
 9693
 9694    editor.update_in(cx, |editor, window, cx| {
 9695        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9696            s.select_ranges([5..5, 8..8, 9..9])
 9697        });
 9698        editor.newline(&Newline, window, cx);
 9699        assert_eq!(
 9700            editor.text(cx),
 9701            indoc!(
 9702                "
 9703                fn a(
 9704
 9705                ) {
 9706
 9707                }
 9708                "
 9709            )
 9710        );
 9711        assert_eq!(
 9712            editor.selections.ranges(&editor.display_snapshot(cx)),
 9713            &[
 9714                Point::new(1, 0)..Point::new(1, 0),
 9715                Point::new(3, 0)..Point::new(3, 0),
 9716                Point::new(5, 0)..Point::new(5, 0)
 9717            ]
 9718        );
 9719    });
 9720}
 9721
 9722#[gpui::test]
 9723async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9724    init_test(cx, |settings| {
 9725        settings.defaults.auto_indent = Some(true);
 9726        settings.languages.0.insert(
 9727            "python".into(),
 9728            LanguageSettingsContent {
 9729                auto_indent: Some(false),
 9730                ..Default::default()
 9731            },
 9732        );
 9733    });
 9734
 9735    let mut cx = EditorTestContext::new(cx).await;
 9736
 9737    let injected_language = Arc::new(
 9738        Language::new(
 9739            LanguageConfig {
 9740                brackets: BracketPairConfig {
 9741                    pairs: vec![
 9742                        BracketPair {
 9743                            start: "{".to_string(),
 9744                            end: "}".to_string(),
 9745                            close: false,
 9746                            surround: false,
 9747                            newline: true,
 9748                        },
 9749                        BracketPair {
 9750                            start: "(".to_string(),
 9751                            end: ")".to_string(),
 9752                            close: true,
 9753                            surround: false,
 9754                            newline: true,
 9755                        },
 9756                    ],
 9757                    ..Default::default()
 9758                },
 9759                name: "python".into(),
 9760                ..Default::default()
 9761            },
 9762            Some(tree_sitter_python::LANGUAGE.into()),
 9763        )
 9764        .with_indents_query(
 9765            r#"
 9766                (_ "(" ")" @end) @indent
 9767                (_ "{" "}" @end) @indent
 9768            "#,
 9769        )
 9770        .unwrap(),
 9771    );
 9772
 9773    let language = Arc::new(
 9774        Language::new(
 9775            LanguageConfig {
 9776                brackets: BracketPairConfig {
 9777                    pairs: vec![
 9778                        BracketPair {
 9779                            start: "{".to_string(),
 9780                            end: "}".to_string(),
 9781                            close: false,
 9782                            surround: false,
 9783                            newline: true,
 9784                        },
 9785                        BracketPair {
 9786                            start: "(".to_string(),
 9787                            end: ")".to_string(),
 9788                            close: true,
 9789                            surround: false,
 9790                            newline: true,
 9791                        },
 9792                    ],
 9793                    ..Default::default()
 9794                },
 9795                name: LanguageName::new("rust"),
 9796                ..Default::default()
 9797            },
 9798            Some(tree_sitter_rust::LANGUAGE.into()),
 9799        )
 9800        .with_indents_query(
 9801            r#"
 9802                (_ "(" ")" @end) @indent
 9803                (_ "{" "}" @end) @indent
 9804            "#,
 9805        )
 9806        .unwrap()
 9807        .with_injection_query(
 9808            r#"
 9809            (macro_invocation
 9810                macro: (identifier) @_macro_name
 9811                (token_tree) @injection.content
 9812                (#set! injection.language "python"))
 9813           "#,
 9814        )
 9815        .unwrap(),
 9816    );
 9817
 9818    cx.language_registry().add(injected_language);
 9819    cx.language_registry().add(language.clone());
 9820
 9821    cx.update_buffer(|buffer, cx| {
 9822        buffer.set_language(Some(language), cx);
 9823    });
 9824
 9825    cx.set_state(r#"struct A {ˇ}"#);
 9826
 9827    cx.update_editor(|editor, window, cx| {
 9828        editor.newline(&Default::default(), window, cx);
 9829    });
 9830
 9831    cx.assert_editor_state(indoc!(
 9832        "struct A {
 9833            ˇ
 9834        }"
 9835    ));
 9836
 9837    cx.set_state(r#"select_biased!(ˇ)"#);
 9838
 9839    cx.update_editor(|editor, window, cx| {
 9840        editor.newline(&Default::default(), window, cx);
 9841        editor.handle_input("def ", window, cx);
 9842        editor.handle_input("(", window, cx);
 9843        editor.newline(&Default::default(), window, cx);
 9844        editor.handle_input("a", window, cx);
 9845    });
 9846
 9847    cx.assert_editor_state(indoc!(
 9848        "select_biased!(
 9849        def (
 9850 9851        )
 9852        )"
 9853    ));
 9854}
 9855
 9856#[gpui::test]
 9857async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9858    init_test(cx, |_| {});
 9859
 9860    {
 9861        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9862        cx.set_state(indoc! {"
 9863            impl A {
 9864
 9865                fn b() {}
 9866
 9867            «fn c() {
 9868
 9869            }ˇ»
 9870            }
 9871        "});
 9872
 9873        cx.update_editor(|editor, window, cx| {
 9874            editor.autoindent(&Default::default(), window, cx);
 9875        });
 9876
 9877        cx.assert_editor_state(indoc! {"
 9878            impl A {
 9879
 9880                fn b() {}
 9881
 9882                «fn c() {
 9883
 9884                }ˇ»
 9885            }
 9886        "});
 9887    }
 9888
 9889    {
 9890        let mut cx = EditorTestContext::new_multibuffer(
 9891            cx,
 9892            [indoc! { "
 9893                impl A {
 9894                «
 9895                // a
 9896                fn b(){}
 9897                »
 9898                «
 9899                    }
 9900                    fn c(){}
 9901                »
 9902            "}],
 9903        );
 9904
 9905        let buffer = cx.update_editor(|editor, _, cx| {
 9906            let buffer = editor.buffer().update(cx, |buffer, _| {
 9907                buffer.all_buffers().iter().next().unwrap().clone()
 9908            });
 9909            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9910            buffer
 9911        });
 9912
 9913        cx.run_until_parked();
 9914        cx.update_editor(|editor, window, cx| {
 9915            editor.select_all(&Default::default(), window, cx);
 9916            editor.autoindent(&Default::default(), window, cx)
 9917        });
 9918        cx.run_until_parked();
 9919
 9920        cx.update(|_, cx| {
 9921            assert_eq!(
 9922                buffer.read(cx).text(),
 9923                indoc! { "
 9924                    impl A {
 9925
 9926                        // a
 9927                        fn b(){}
 9928
 9929
 9930                    }
 9931                    fn c(){}
 9932
 9933                " }
 9934            )
 9935        });
 9936    }
 9937}
 9938
 9939#[gpui::test]
 9940async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9941    init_test(cx, |_| {});
 9942
 9943    let mut cx = EditorTestContext::new(cx).await;
 9944
 9945    let language = Arc::new(Language::new(
 9946        LanguageConfig {
 9947            brackets: BracketPairConfig {
 9948                pairs: vec![
 9949                    BracketPair {
 9950                        start: "{".to_string(),
 9951                        end: "}".to_string(),
 9952                        close: true,
 9953                        surround: true,
 9954                        newline: true,
 9955                    },
 9956                    BracketPair {
 9957                        start: "(".to_string(),
 9958                        end: ")".to_string(),
 9959                        close: true,
 9960                        surround: true,
 9961                        newline: true,
 9962                    },
 9963                    BracketPair {
 9964                        start: "/*".to_string(),
 9965                        end: " */".to_string(),
 9966                        close: true,
 9967                        surround: true,
 9968                        newline: true,
 9969                    },
 9970                    BracketPair {
 9971                        start: "[".to_string(),
 9972                        end: "]".to_string(),
 9973                        close: false,
 9974                        surround: false,
 9975                        newline: true,
 9976                    },
 9977                    BracketPair {
 9978                        start: "\"".to_string(),
 9979                        end: "\"".to_string(),
 9980                        close: true,
 9981                        surround: true,
 9982                        newline: false,
 9983                    },
 9984                    BracketPair {
 9985                        start: "<".to_string(),
 9986                        end: ">".to_string(),
 9987                        close: false,
 9988                        surround: true,
 9989                        newline: true,
 9990                    },
 9991                ],
 9992                ..Default::default()
 9993            },
 9994            autoclose_before: "})]".to_string(),
 9995            ..Default::default()
 9996        },
 9997        Some(tree_sitter_rust::LANGUAGE.into()),
 9998    ));
 9999
10000    cx.language_registry().add(language.clone());
10001    cx.update_buffer(|buffer, cx| {
10002        buffer.set_language(Some(language), cx);
10003    });
10004
10005    cx.set_state(
10006        &r#"
10007            🏀ˇ
10008            εˇ
10009            ❤️ˇ
10010        "#
10011        .unindent(),
10012    );
10013
10014    // autoclose multiple nested brackets at multiple cursors
10015    cx.update_editor(|editor, window, cx| {
10016        editor.handle_input("{", window, cx);
10017        editor.handle_input("{", window, cx);
10018        editor.handle_input("{", window, cx);
10019    });
10020    cx.assert_editor_state(
10021        &"
10022            🏀{{{ˇ}}}
10023            ε{{{ˇ}}}
10024            ❤️{{{ˇ}}}
10025        "
10026        .unindent(),
10027    );
10028
10029    // insert a different closing bracket
10030    cx.update_editor(|editor, window, cx| {
10031        editor.handle_input(")", window, cx);
10032    });
10033    cx.assert_editor_state(
10034        &"
10035            🏀{{{)ˇ}}}
10036            ε{{{)ˇ}}}
10037            ❤️{{{)ˇ}}}
10038        "
10039        .unindent(),
10040    );
10041
10042    // skip over the auto-closed brackets when typing a closing bracket
10043    cx.update_editor(|editor, window, cx| {
10044        editor.move_right(&MoveRight, window, cx);
10045        editor.handle_input("}", window, cx);
10046        editor.handle_input("}", window, cx);
10047        editor.handle_input("}", window, cx);
10048    });
10049    cx.assert_editor_state(
10050        &"
10051            🏀{{{)}}}}ˇ
10052            ε{{{)}}}}ˇ
10053            ❤️{{{)}}}}ˇ
10054        "
10055        .unindent(),
10056    );
10057
10058    // autoclose multi-character pairs
10059    cx.set_state(
10060        &"
10061            ˇ
10062            ˇ
10063        "
10064        .unindent(),
10065    );
10066    cx.update_editor(|editor, window, cx| {
10067        editor.handle_input("/", window, cx);
10068        editor.handle_input("*", window, cx);
10069    });
10070    cx.assert_editor_state(
10071        &"
10072            /*ˇ */
10073            /*ˇ */
10074        "
10075        .unindent(),
10076    );
10077
10078    // one cursor autocloses a multi-character pair, one cursor
10079    // does not autoclose.
10080    cx.set_state(
10081        &"
1008210083            ˇ
10084        "
10085        .unindent(),
10086    );
10087    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10088    cx.assert_editor_state(
10089        &"
10090            /*ˇ */
1009110092        "
10093        .unindent(),
10094    );
10095
10096    // Don't autoclose if the next character isn't whitespace and isn't
10097    // listed in the language's "autoclose_before" section.
10098    cx.set_state("ˇa b");
10099    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10100    cx.assert_editor_state("{ˇa b");
10101
10102    // Don't autoclose if `close` is false for the bracket pair
10103    cx.set_state("ˇ");
10104    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10105    cx.assert_editor_state("");
10106
10107    // Surround with brackets if text is selected
10108    cx.set_state("«aˇ» b");
10109    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10110    cx.assert_editor_state("{«aˇ»} b");
10111
10112    // Autoclose when not immediately after a word character
10113    cx.set_state("a ˇ");
10114    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10115    cx.assert_editor_state("a \"ˇ\"");
10116
10117    // Autoclose pair where the start and end characters are the same
10118    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10119    cx.assert_editor_state("a \"\"ˇ");
10120
10121    // Don't autoclose when immediately after a word character
10122    cx.set_state("");
10123    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10124    cx.assert_editor_state("a\"ˇ");
10125
10126    // Do autoclose when after a non-word character
10127    cx.set_state("");
10128    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10129    cx.assert_editor_state("{\"ˇ\"");
10130
10131    // Non identical pairs autoclose regardless of preceding character
10132    cx.set_state("");
10133    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10134    cx.assert_editor_state("a{ˇ}");
10135
10136    // Don't autoclose pair if autoclose is disabled
10137    cx.set_state("ˇ");
10138    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10139    cx.assert_editor_state("");
10140
10141    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10142    cx.set_state("«aˇ» b");
10143    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10144    cx.assert_editor_state("<«aˇ»> b");
10145}
10146
10147#[gpui::test]
10148async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10149    init_test(cx, |settings| {
10150        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10151    });
10152
10153    let mut cx = EditorTestContext::new(cx).await;
10154
10155    let language = Arc::new(Language::new(
10156        LanguageConfig {
10157            brackets: BracketPairConfig {
10158                pairs: vec![
10159                    BracketPair {
10160                        start: "{".to_string(),
10161                        end: "}".to_string(),
10162                        close: true,
10163                        surround: true,
10164                        newline: true,
10165                    },
10166                    BracketPair {
10167                        start: "(".to_string(),
10168                        end: ")".to_string(),
10169                        close: true,
10170                        surround: true,
10171                        newline: true,
10172                    },
10173                    BracketPair {
10174                        start: "[".to_string(),
10175                        end: "]".to_string(),
10176                        close: false,
10177                        surround: false,
10178                        newline: true,
10179                    },
10180                ],
10181                ..Default::default()
10182            },
10183            autoclose_before: "})]".to_string(),
10184            ..Default::default()
10185        },
10186        Some(tree_sitter_rust::LANGUAGE.into()),
10187    ));
10188
10189    cx.language_registry().add(language.clone());
10190    cx.update_buffer(|buffer, cx| {
10191        buffer.set_language(Some(language), cx);
10192    });
10193
10194    cx.set_state(
10195        &"
10196            ˇ
10197            ˇ
10198            ˇ
10199        "
10200        .unindent(),
10201    );
10202
10203    // ensure only matching closing brackets are skipped over
10204    cx.update_editor(|editor, window, cx| {
10205        editor.handle_input("}", window, cx);
10206        editor.move_left(&MoveLeft, window, cx);
10207        editor.handle_input(")", window, cx);
10208        editor.move_left(&MoveLeft, window, cx);
10209    });
10210    cx.assert_editor_state(
10211        &"
10212            ˇ)}
10213            ˇ)}
10214            ˇ)}
10215        "
10216        .unindent(),
10217    );
10218
10219    // skip-over closing brackets at multiple cursors
10220    cx.update_editor(|editor, window, cx| {
10221        editor.handle_input(")", window, cx);
10222        editor.handle_input("}", window, cx);
10223    });
10224    cx.assert_editor_state(
10225        &"
10226            )}ˇ
10227            )}ˇ
10228            )}ˇ
10229        "
10230        .unindent(),
10231    );
10232
10233    // ignore non-close brackets
10234    cx.update_editor(|editor, window, cx| {
10235        editor.handle_input("]", window, cx);
10236        editor.move_left(&MoveLeft, window, cx);
10237        editor.handle_input("]", window, cx);
10238    });
10239    cx.assert_editor_state(
10240        &"
10241            )}]ˇ]
10242            )}]ˇ]
10243            )}]ˇ]
10244        "
10245        .unindent(),
10246    );
10247}
10248
10249#[gpui::test]
10250async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10251    init_test(cx, |_| {});
10252
10253    let mut cx = EditorTestContext::new(cx).await;
10254
10255    let html_language = Arc::new(
10256        Language::new(
10257            LanguageConfig {
10258                name: "HTML".into(),
10259                brackets: BracketPairConfig {
10260                    pairs: vec![
10261                        BracketPair {
10262                            start: "<".into(),
10263                            end: ">".into(),
10264                            close: true,
10265                            ..Default::default()
10266                        },
10267                        BracketPair {
10268                            start: "{".into(),
10269                            end: "}".into(),
10270                            close: true,
10271                            ..Default::default()
10272                        },
10273                        BracketPair {
10274                            start: "(".into(),
10275                            end: ")".into(),
10276                            close: true,
10277                            ..Default::default()
10278                        },
10279                    ],
10280                    ..Default::default()
10281                },
10282                autoclose_before: "})]>".into(),
10283                ..Default::default()
10284            },
10285            Some(tree_sitter_html::LANGUAGE.into()),
10286        )
10287        .with_injection_query(
10288            r#"
10289            (script_element
10290                (raw_text) @injection.content
10291                (#set! injection.language "javascript"))
10292            "#,
10293        )
10294        .unwrap(),
10295    );
10296
10297    let javascript_language = Arc::new(Language::new(
10298        LanguageConfig {
10299            name: "JavaScript".into(),
10300            brackets: BracketPairConfig {
10301                pairs: vec![
10302                    BracketPair {
10303                        start: "/*".into(),
10304                        end: " */".into(),
10305                        close: true,
10306                        ..Default::default()
10307                    },
10308                    BracketPair {
10309                        start: "{".into(),
10310                        end: "}".into(),
10311                        close: true,
10312                        ..Default::default()
10313                    },
10314                    BracketPair {
10315                        start: "(".into(),
10316                        end: ")".into(),
10317                        close: true,
10318                        ..Default::default()
10319                    },
10320                ],
10321                ..Default::default()
10322            },
10323            autoclose_before: "})]>".into(),
10324            ..Default::default()
10325        },
10326        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10327    ));
10328
10329    cx.language_registry().add(html_language.clone());
10330    cx.language_registry().add(javascript_language);
10331    cx.executor().run_until_parked();
10332
10333    cx.update_buffer(|buffer, cx| {
10334        buffer.set_language(Some(html_language), cx);
10335    });
10336
10337    cx.set_state(
10338        &r#"
10339            <body>ˇ
10340                <script>
10341                    var x = 1;ˇ
10342                </script>
10343            </body>ˇ
10344        "#
10345        .unindent(),
10346    );
10347
10348    // Precondition: different languages are active at different locations.
10349    cx.update_editor(|editor, window, cx| {
10350        let snapshot = editor.snapshot(window, cx);
10351        let cursors = editor
10352            .selections
10353            .ranges::<usize>(&editor.display_snapshot(cx));
10354        let languages = cursors
10355            .iter()
10356            .map(|c| snapshot.language_at(c.start).unwrap().name())
10357            .collect::<Vec<_>>();
10358        assert_eq!(
10359            languages,
10360            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10361        );
10362    });
10363
10364    // Angle brackets autoclose in HTML, but not JavaScript.
10365    cx.update_editor(|editor, window, cx| {
10366        editor.handle_input("<", window, cx);
10367        editor.handle_input("a", window, cx);
10368    });
10369    cx.assert_editor_state(
10370        &r#"
10371            <body><aˇ>
10372                <script>
10373                    var x = 1;<aˇ
10374                </script>
10375            </body><aˇ>
10376        "#
10377        .unindent(),
10378    );
10379
10380    // Curly braces and parens autoclose in both HTML and JavaScript.
10381    cx.update_editor(|editor, window, cx| {
10382        editor.handle_input(" b=", window, cx);
10383        editor.handle_input("{", window, cx);
10384        editor.handle_input("c", window, cx);
10385        editor.handle_input("(", window, cx);
10386    });
10387    cx.assert_editor_state(
10388        &r#"
10389            <body><a b={c(ˇ)}>
10390                <script>
10391                    var x = 1;<a b={c(ˇ)}
10392                </script>
10393            </body><a b={c(ˇ)}>
10394        "#
10395        .unindent(),
10396    );
10397
10398    // Brackets that were already autoclosed are skipped.
10399    cx.update_editor(|editor, window, cx| {
10400        editor.handle_input(")", window, cx);
10401        editor.handle_input("d", window, cx);
10402        editor.handle_input("}", window, cx);
10403    });
10404    cx.assert_editor_state(
10405        &r#"
10406            <body><a b={c()d}ˇ>
10407                <script>
10408                    var x = 1;<a b={c()d}ˇ
10409                </script>
10410            </body><a b={c()d}ˇ>
10411        "#
10412        .unindent(),
10413    );
10414    cx.update_editor(|editor, window, cx| {
10415        editor.handle_input(">", window, cx);
10416    });
10417    cx.assert_editor_state(
10418        &r#"
10419            <body><a b={c()d}>ˇ
10420                <script>
10421                    var x = 1;<a b={c()d}>ˇ
10422                </script>
10423            </body><a b={c()d}>ˇ
10424        "#
10425        .unindent(),
10426    );
10427
10428    // Reset
10429    cx.set_state(
10430        &r#"
10431            <body>ˇ
10432                <script>
10433                    var x = 1;ˇ
10434                </script>
10435            </body>ˇ
10436        "#
10437        .unindent(),
10438    );
10439
10440    cx.update_editor(|editor, window, cx| {
10441        editor.handle_input("<", window, cx);
10442    });
10443    cx.assert_editor_state(
10444        &r#"
10445            <body><ˇ>
10446                <script>
10447                    var x = 1;<ˇ
10448                </script>
10449            </body><ˇ>
10450        "#
10451        .unindent(),
10452    );
10453
10454    // When backspacing, the closing angle brackets are removed.
10455    cx.update_editor(|editor, window, cx| {
10456        editor.backspace(&Backspace, window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body>ˇ
10461                <script>
10462                    var x = 1;ˇ
10463                </script>
10464            </body>ˇ
10465        "#
10466        .unindent(),
10467    );
10468
10469    // Block comments autoclose in JavaScript, but not HTML.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.handle_input("/", window, cx);
10472        editor.handle_input("*", window, cx);
10473    });
10474    cx.assert_editor_state(
10475        &r#"
10476            <body>/*ˇ
10477                <script>
10478                    var x = 1;/*ˇ */
10479                </script>
10480            </body>/*ˇ
10481        "#
10482        .unindent(),
10483    );
10484}
10485
10486#[gpui::test]
10487async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10488    init_test(cx, |_| {});
10489
10490    let mut cx = EditorTestContext::new(cx).await;
10491
10492    let rust_language = Arc::new(
10493        Language::new(
10494            LanguageConfig {
10495                name: "Rust".into(),
10496                brackets: serde_json::from_value(json!([
10497                    { "start": "{", "end": "}", "close": true, "newline": true },
10498                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10499                ]))
10500                .unwrap(),
10501                autoclose_before: "})]>".into(),
10502                ..Default::default()
10503            },
10504            Some(tree_sitter_rust::LANGUAGE.into()),
10505        )
10506        .with_override_query("(string_literal) @string")
10507        .unwrap(),
10508    );
10509
10510    cx.language_registry().add(rust_language.clone());
10511    cx.update_buffer(|buffer, cx| {
10512        buffer.set_language(Some(rust_language), cx);
10513    });
10514
10515    cx.set_state(
10516        &r#"
10517            let x = ˇ
10518        "#
10519        .unindent(),
10520    );
10521
10522    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10523    cx.update_editor(|editor, window, cx| {
10524        editor.handle_input("\"", window, cx);
10525    });
10526    cx.assert_editor_state(
10527        &r#"
10528            let x = "ˇ"
10529        "#
10530        .unindent(),
10531    );
10532
10533    // Inserting another quotation mark. The cursor moves across the existing
10534    // automatically-inserted quotation mark.
10535    cx.update_editor(|editor, window, cx| {
10536        editor.handle_input("\"", window, cx);
10537    });
10538    cx.assert_editor_state(
10539        &r#"
10540            let x = ""ˇ
10541        "#
10542        .unindent(),
10543    );
10544
10545    // Reset
10546    cx.set_state(
10547        &r#"
10548            let x = ˇ
10549        "#
10550        .unindent(),
10551    );
10552
10553    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10554    cx.update_editor(|editor, window, cx| {
10555        editor.handle_input("\"", window, cx);
10556        editor.handle_input(" ", window, cx);
10557        editor.move_left(&Default::default(), window, cx);
10558        editor.handle_input("\\", window, cx);
10559        editor.handle_input("\"", window, cx);
10560    });
10561    cx.assert_editor_state(
10562        &r#"
10563            let x = "\"ˇ "
10564        "#
10565        .unindent(),
10566    );
10567
10568    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10569    // mark. Nothing is inserted.
10570    cx.update_editor(|editor, window, cx| {
10571        editor.move_right(&Default::default(), window, cx);
10572        editor.handle_input("\"", window, cx);
10573    });
10574    cx.assert_editor_state(
10575        &r#"
10576            let x = "\" "ˇ
10577        "#
10578        .unindent(),
10579    );
10580}
10581
10582#[gpui::test]
10583async fn test_surround_with_pair(cx: &mut TestAppContext) {
10584    init_test(cx, |_| {});
10585
10586    let language = Arc::new(Language::new(
10587        LanguageConfig {
10588            brackets: BracketPairConfig {
10589                pairs: vec![
10590                    BracketPair {
10591                        start: "{".to_string(),
10592                        end: "}".to_string(),
10593                        close: true,
10594                        surround: true,
10595                        newline: true,
10596                    },
10597                    BracketPair {
10598                        start: "/* ".to_string(),
10599                        end: "*/".to_string(),
10600                        close: true,
10601                        surround: true,
10602                        ..Default::default()
10603                    },
10604                ],
10605                ..Default::default()
10606            },
10607            ..Default::default()
10608        },
10609        Some(tree_sitter_rust::LANGUAGE.into()),
10610    ));
10611
10612    let text = r#"
10613        a
10614        b
10615        c
10616    "#
10617    .unindent();
10618
10619    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10620    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10621    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10622    editor
10623        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10624        .await;
10625
10626    editor.update_in(cx, |editor, window, cx| {
10627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10628            s.select_display_ranges([
10629                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10630                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10631                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10632            ])
10633        });
10634
10635        editor.handle_input("{", window, cx);
10636        editor.handle_input("{", window, cx);
10637        editor.handle_input("{", window, cx);
10638        assert_eq!(
10639            editor.text(cx),
10640            "
10641                {{{a}}}
10642                {{{b}}}
10643                {{{c}}}
10644            "
10645            .unindent()
10646        );
10647        assert_eq!(
10648            editor.selections.display_ranges(cx),
10649            [
10650                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10651                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10652                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10653            ]
10654        );
10655
10656        editor.undo(&Undo, window, cx);
10657        editor.undo(&Undo, window, cx);
10658        editor.undo(&Undo, window, cx);
10659        assert_eq!(
10660            editor.text(cx),
10661            "
10662                a
10663                b
10664                c
10665            "
10666            .unindent()
10667        );
10668        assert_eq!(
10669            editor.selections.display_ranges(cx),
10670            [
10671                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10672                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10673                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10674            ]
10675        );
10676
10677        // Ensure inserting the first character of a multi-byte bracket pair
10678        // doesn't surround the selections with the bracket.
10679        editor.handle_input("/", window, cx);
10680        assert_eq!(
10681            editor.text(cx),
10682            "
10683                /
10684                /
10685                /
10686            "
10687            .unindent()
10688        );
10689        assert_eq!(
10690            editor.selections.display_ranges(cx),
10691            [
10692                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10693                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10694                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10695            ]
10696        );
10697
10698        editor.undo(&Undo, window, cx);
10699        assert_eq!(
10700            editor.text(cx),
10701            "
10702                a
10703                b
10704                c
10705            "
10706            .unindent()
10707        );
10708        assert_eq!(
10709            editor.selections.display_ranges(cx),
10710            [
10711                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10712                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10713                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10714            ]
10715        );
10716
10717        // Ensure inserting the last character of a multi-byte bracket pair
10718        // doesn't surround the selections with the bracket.
10719        editor.handle_input("*", window, cx);
10720        assert_eq!(
10721            editor.text(cx),
10722            "
10723                *
10724                *
10725                *
10726            "
10727            .unindent()
10728        );
10729        assert_eq!(
10730            editor.selections.display_ranges(cx),
10731            [
10732                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10733                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10734                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10735            ]
10736        );
10737    });
10738}
10739
10740#[gpui::test]
10741async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10742    init_test(cx, |_| {});
10743
10744    let language = Arc::new(Language::new(
10745        LanguageConfig {
10746            brackets: BracketPairConfig {
10747                pairs: vec![BracketPair {
10748                    start: "{".to_string(),
10749                    end: "}".to_string(),
10750                    close: true,
10751                    surround: true,
10752                    newline: true,
10753                }],
10754                ..Default::default()
10755            },
10756            autoclose_before: "}".to_string(),
10757            ..Default::default()
10758        },
10759        Some(tree_sitter_rust::LANGUAGE.into()),
10760    ));
10761
10762    let text = r#"
10763        a
10764        b
10765        c
10766    "#
10767    .unindent();
10768
10769    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10770    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10771    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10772    editor
10773        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10774        .await;
10775
10776    editor.update_in(cx, |editor, window, cx| {
10777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10778            s.select_ranges([
10779                Point::new(0, 1)..Point::new(0, 1),
10780                Point::new(1, 1)..Point::new(1, 1),
10781                Point::new(2, 1)..Point::new(2, 1),
10782            ])
10783        });
10784
10785        editor.handle_input("{", window, cx);
10786        editor.handle_input("{", window, cx);
10787        editor.handle_input("_", window, cx);
10788        assert_eq!(
10789            editor.text(cx),
10790            "
10791                a{{_}}
10792                b{{_}}
10793                c{{_}}
10794            "
10795            .unindent()
10796        );
10797        assert_eq!(
10798            editor
10799                .selections
10800                .ranges::<Point>(&editor.display_snapshot(cx)),
10801            [
10802                Point::new(0, 4)..Point::new(0, 4),
10803                Point::new(1, 4)..Point::new(1, 4),
10804                Point::new(2, 4)..Point::new(2, 4)
10805            ]
10806        );
10807
10808        editor.backspace(&Default::default(), window, cx);
10809        editor.backspace(&Default::default(), window, cx);
10810        assert_eq!(
10811            editor.text(cx),
10812            "
10813                a{}
10814                b{}
10815                c{}
10816            "
10817            .unindent()
10818        );
10819        assert_eq!(
10820            editor
10821                .selections
10822                .ranges::<Point>(&editor.display_snapshot(cx)),
10823            [
10824                Point::new(0, 2)..Point::new(0, 2),
10825                Point::new(1, 2)..Point::new(1, 2),
10826                Point::new(2, 2)..Point::new(2, 2)
10827            ]
10828        );
10829
10830        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10831        assert_eq!(
10832            editor.text(cx),
10833            "
10834                a
10835                b
10836                c
10837            "
10838            .unindent()
10839        );
10840        assert_eq!(
10841            editor
10842                .selections
10843                .ranges::<Point>(&editor.display_snapshot(cx)),
10844            [
10845                Point::new(0, 1)..Point::new(0, 1),
10846                Point::new(1, 1)..Point::new(1, 1),
10847                Point::new(2, 1)..Point::new(2, 1)
10848            ]
10849        );
10850    });
10851}
10852
10853#[gpui::test]
10854async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10855    init_test(cx, |settings| {
10856        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10857    });
10858
10859    let mut cx = EditorTestContext::new(cx).await;
10860
10861    let language = Arc::new(Language::new(
10862        LanguageConfig {
10863            brackets: BracketPairConfig {
10864                pairs: vec![
10865                    BracketPair {
10866                        start: "{".to_string(),
10867                        end: "}".to_string(),
10868                        close: true,
10869                        surround: true,
10870                        newline: true,
10871                    },
10872                    BracketPair {
10873                        start: "(".to_string(),
10874                        end: ")".to_string(),
10875                        close: true,
10876                        surround: true,
10877                        newline: true,
10878                    },
10879                    BracketPair {
10880                        start: "[".to_string(),
10881                        end: "]".to_string(),
10882                        close: false,
10883                        surround: true,
10884                        newline: true,
10885                    },
10886                ],
10887                ..Default::default()
10888            },
10889            autoclose_before: "})]".to_string(),
10890            ..Default::default()
10891        },
10892        Some(tree_sitter_rust::LANGUAGE.into()),
10893    ));
10894
10895    cx.language_registry().add(language.clone());
10896    cx.update_buffer(|buffer, cx| {
10897        buffer.set_language(Some(language), cx);
10898    });
10899
10900    cx.set_state(
10901        &"
10902            {(ˇ)}
10903            [[ˇ]]
10904            {(ˇ)}
10905        "
10906        .unindent(),
10907    );
10908
10909    cx.update_editor(|editor, window, cx| {
10910        editor.backspace(&Default::default(), window, cx);
10911        editor.backspace(&Default::default(), window, cx);
10912    });
10913
10914    cx.assert_editor_state(
10915        &"
10916            ˇ
10917            ˇ]]
10918            ˇ
10919        "
10920        .unindent(),
10921    );
10922
10923    cx.update_editor(|editor, window, cx| {
10924        editor.handle_input("{", window, cx);
10925        editor.handle_input("{", window, cx);
10926        editor.move_right(&MoveRight, window, cx);
10927        editor.move_right(&MoveRight, window, cx);
10928        editor.move_left(&MoveLeft, window, cx);
10929        editor.move_left(&MoveLeft, window, cx);
10930        editor.backspace(&Default::default(), window, cx);
10931    });
10932
10933    cx.assert_editor_state(
10934        &"
10935            {ˇ}
10936            {ˇ}]]
10937            {ˇ}
10938        "
10939        .unindent(),
10940    );
10941
10942    cx.update_editor(|editor, window, cx| {
10943        editor.backspace(&Default::default(), window, cx);
10944    });
10945
10946    cx.assert_editor_state(
10947        &"
10948            ˇ
10949            ˇ]]
10950            ˇ
10951        "
10952        .unindent(),
10953    );
10954}
10955
10956#[gpui::test]
10957async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10958    init_test(cx, |_| {});
10959
10960    let language = Arc::new(Language::new(
10961        LanguageConfig::default(),
10962        Some(tree_sitter_rust::LANGUAGE.into()),
10963    ));
10964
10965    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10966    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10967    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10968    editor
10969        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10970        .await;
10971
10972    editor.update_in(cx, |editor, window, cx| {
10973        editor.set_auto_replace_emoji_shortcode(true);
10974
10975        editor.handle_input("Hello ", window, cx);
10976        editor.handle_input(":wave", window, cx);
10977        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10978
10979        editor.handle_input(":", window, cx);
10980        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10981
10982        editor.handle_input(" :smile", window, cx);
10983        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10984
10985        editor.handle_input(":", window, cx);
10986        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10987
10988        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10989        editor.handle_input(":wave", window, cx);
10990        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10991
10992        editor.handle_input(":", window, cx);
10993        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10994
10995        editor.handle_input(":1", window, cx);
10996        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10997
10998        editor.handle_input(":", window, cx);
10999        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11000
11001        // Ensure shortcode does not get replaced when it is part of a word
11002        editor.handle_input(" Test:wave", window, cx);
11003        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11004
11005        editor.handle_input(":", window, cx);
11006        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11007
11008        editor.set_auto_replace_emoji_shortcode(false);
11009
11010        // Ensure shortcode does not get replaced when auto replace is off
11011        editor.handle_input(" :wave", window, cx);
11012        assert_eq!(
11013            editor.text(cx),
11014            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11015        );
11016
11017        editor.handle_input(":", window, cx);
11018        assert_eq!(
11019            editor.text(cx),
11020            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11021        );
11022    });
11023}
11024
11025#[gpui::test]
11026async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11027    init_test(cx, |_| {});
11028
11029    let (text, insertion_ranges) = marked_text_ranges(
11030        indoc! {"
11031            ˇ
11032        "},
11033        false,
11034    );
11035
11036    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11037    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11038
11039    _ = editor.update_in(cx, |editor, window, cx| {
11040        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11041
11042        editor
11043            .insert_snippet(&insertion_ranges, snippet, window, cx)
11044            .unwrap();
11045
11046        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11047            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11048            assert_eq!(editor.text(cx), expected_text);
11049            assert_eq!(
11050                editor
11051                    .selections
11052                    .ranges::<usize>(&editor.display_snapshot(cx)),
11053                selection_ranges
11054            );
11055        }
11056
11057        assert(
11058            editor,
11059            cx,
11060            indoc! {"
11061            type «» =•
11062            "},
11063        );
11064
11065        assert!(editor.context_menu_visible(), "There should be a matches");
11066    });
11067}
11068
11069#[gpui::test]
11070async fn test_snippets(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    let mut cx = EditorTestContext::new(cx).await;
11074
11075    cx.set_state(indoc! {"
11076        a.ˇ b
11077        a.ˇ b
11078        a.ˇ b
11079    "});
11080
11081    cx.update_editor(|editor, window, cx| {
11082        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11083        let insertion_ranges = editor
11084            .selections
11085            .all(&editor.display_snapshot(cx))
11086            .iter()
11087            .map(|s| s.range())
11088            .collect::<Vec<_>>();
11089        editor
11090            .insert_snippet(&insertion_ranges, snippet, window, cx)
11091            .unwrap();
11092    });
11093
11094    cx.assert_editor_state(indoc! {"
11095        a.f(«oneˇ», two, «threeˇ») b
11096        a.f(«oneˇ», two, «threeˇ») b
11097        a.f(«oneˇ», two, «threeˇ») b
11098    "});
11099
11100    // Can't move earlier than the first tab stop
11101    cx.update_editor(|editor, window, cx| {
11102        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11103    });
11104    cx.assert_editor_state(indoc! {"
11105        a.f(«oneˇ», two, «threeˇ») b
11106        a.f(«oneˇ», two, «threeˇ») b
11107        a.f(«oneˇ», two, «threeˇ») b
11108    "});
11109
11110    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11111    cx.assert_editor_state(indoc! {"
11112        a.f(one, «twoˇ», three) b
11113        a.f(one, «twoˇ», three) b
11114        a.f(one, «twoˇ», three) b
11115    "});
11116
11117    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11118    cx.assert_editor_state(indoc! {"
11119        a.f(«oneˇ», two, «threeˇ») b
11120        a.f(«oneˇ», two, «threeˇ») b
11121        a.f(«oneˇ», two, «threeˇ») b
11122    "});
11123
11124    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11125    cx.assert_editor_state(indoc! {"
11126        a.f(one, «twoˇ», three) b
11127        a.f(one, «twoˇ», three) b
11128        a.f(one, «twoˇ», three) b
11129    "});
11130    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11131    cx.assert_editor_state(indoc! {"
11132        a.f(one, two, three)ˇ b
11133        a.f(one, two, three)ˇ b
11134        a.f(one, two, three)ˇ b
11135    "});
11136
11137    // As soon as the last tab stop is reached, snippet state is gone
11138    cx.update_editor(|editor, window, cx| {
11139        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11140    });
11141    cx.assert_editor_state(indoc! {"
11142        a.f(one, two, three)ˇ b
11143        a.f(one, two, three)ˇ b
11144        a.f(one, two, three)ˇ b
11145    "});
11146}
11147
11148#[gpui::test]
11149async fn test_snippet_indentation(cx: &mut TestAppContext) {
11150    init_test(cx, |_| {});
11151
11152    let mut cx = EditorTestContext::new(cx).await;
11153
11154    cx.update_editor(|editor, window, cx| {
11155        let snippet = Snippet::parse(indoc! {"
11156            /*
11157             * Multiline comment with leading indentation
11158             *
11159             * $1
11160             */
11161            $0"})
11162        .unwrap();
11163        let insertion_ranges = editor
11164            .selections
11165            .all(&editor.display_snapshot(cx))
11166            .iter()
11167            .map(|s| s.range())
11168            .collect::<Vec<_>>();
11169        editor
11170            .insert_snippet(&insertion_ranges, snippet, window, cx)
11171            .unwrap();
11172    });
11173
11174    cx.assert_editor_state(indoc! {"
11175        /*
11176         * Multiline comment with leading indentation
11177         *
11178         * ˇ
11179         */
11180    "});
11181
11182    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11183    cx.assert_editor_state(indoc! {"
11184        /*
11185         * Multiline comment with leading indentation
11186         *
11187         *•
11188         */
11189        ˇ"});
11190}
11191
11192#[gpui::test]
11193async fn test_document_format_during_save(cx: &mut TestAppContext) {
11194    init_test(cx, |_| {});
11195
11196    let fs = FakeFs::new(cx.executor());
11197    fs.insert_file(path!("/file.rs"), Default::default()).await;
11198
11199    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11200
11201    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11202    language_registry.add(rust_lang());
11203    let mut fake_servers = language_registry.register_fake_lsp(
11204        "Rust",
11205        FakeLspAdapter {
11206            capabilities: lsp::ServerCapabilities {
11207                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11208                ..Default::default()
11209            },
11210            ..Default::default()
11211        },
11212    );
11213
11214    let buffer = project
11215        .update(cx, |project, cx| {
11216            project.open_local_buffer(path!("/file.rs"), cx)
11217        })
11218        .await
11219        .unwrap();
11220
11221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11222    let (editor, cx) = cx.add_window_view(|window, cx| {
11223        build_editor_with_project(project.clone(), buffer, window, cx)
11224    });
11225    editor.update_in(cx, |editor, window, cx| {
11226        editor.set_text("one\ntwo\nthree\n", window, cx)
11227    });
11228    assert!(cx.read(|cx| editor.is_dirty(cx)));
11229
11230    cx.executor().start_waiting();
11231    let fake_server = fake_servers.next().await.unwrap();
11232
11233    {
11234        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11235            move |params, _| async move {
11236                assert_eq!(
11237                    params.text_document.uri,
11238                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11239                );
11240                assert_eq!(params.options.tab_size, 4);
11241                Ok(Some(vec![lsp::TextEdit::new(
11242                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11243                    ", ".to_string(),
11244                )]))
11245            },
11246        );
11247        let save = editor
11248            .update_in(cx, |editor, window, cx| {
11249                editor.save(
11250                    SaveOptions {
11251                        format: true,
11252                        autosave: false,
11253                    },
11254                    project.clone(),
11255                    window,
11256                    cx,
11257                )
11258            })
11259            .unwrap();
11260        cx.executor().start_waiting();
11261        save.await;
11262
11263        assert_eq!(
11264            editor.update(cx, |editor, cx| editor.text(cx)),
11265            "one, two\nthree\n"
11266        );
11267        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11268    }
11269
11270    {
11271        editor.update_in(cx, |editor, window, cx| {
11272            editor.set_text("one\ntwo\nthree\n", window, cx)
11273        });
11274        assert!(cx.read(|cx| editor.is_dirty(cx)));
11275
11276        // Ensure we can still save even if formatting hangs.
11277        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11278            move |params, _| async move {
11279                assert_eq!(
11280                    params.text_document.uri,
11281                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11282                );
11283                futures::future::pending::<()>().await;
11284                unreachable!()
11285            },
11286        );
11287        let save = editor
11288            .update_in(cx, |editor, window, cx| {
11289                editor.save(
11290                    SaveOptions {
11291                        format: true,
11292                        autosave: false,
11293                    },
11294                    project.clone(),
11295                    window,
11296                    cx,
11297                )
11298            })
11299            .unwrap();
11300        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11301        cx.executor().start_waiting();
11302        save.await;
11303        assert_eq!(
11304            editor.update(cx, |editor, cx| editor.text(cx)),
11305            "one\ntwo\nthree\n"
11306        );
11307    }
11308
11309    // Set rust language override and assert overridden tabsize is sent to language server
11310    update_test_language_settings(cx, |settings| {
11311        settings.languages.0.insert(
11312            "Rust".into(),
11313            LanguageSettingsContent {
11314                tab_size: NonZeroU32::new(8),
11315                ..Default::default()
11316            },
11317        );
11318    });
11319
11320    {
11321        editor.update_in(cx, |editor, window, cx| {
11322            editor.set_text("somehting_new\n", window, cx)
11323        });
11324        assert!(cx.read(|cx| editor.is_dirty(cx)));
11325        let _formatting_request_signal = fake_server
11326            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11327                assert_eq!(
11328                    params.text_document.uri,
11329                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11330                );
11331                assert_eq!(params.options.tab_size, 8);
11332                Ok(Some(vec![]))
11333            });
11334        let save = editor
11335            .update_in(cx, |editor, window, cx| {
11336                editor.save(
11337                    SaveOptions {
11338                        format: true,
11339                        autosave: false,
11340                    },
11341                    project.clone(),
11342                    window,
11343                    cx,
11344                )
11345            })
11346            .unwrap();
11347        cx.executor().start_waiting();
11348        save.await;
11349    }
11350}
11351
11352#[gpui::test]
11353async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11354    init_test(cx, |settings| {
11355        settings.defaults.ensure_final_newline_on_save = Some(false);
11356    });
11357
11358    let fs = FakeFs::new(cx.executor());
11359    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11360
11361    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11362
11363    let buffer = project
11364        .update(cx, |project, cx| {
11365            project.open_local_buffer(path!("/file.txt"), cx)
11366        })
11367        .await
11368        .unwrap();
11369
11370    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11371    let (editor, cx) = cx.add_window_view(|window, cx| {
11372        build_editor_with_project(project.clone(), buffer, window, cx)
11373    });
11374    editor.update_in(cx, |editor, window, cx| {
11375        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11376            s.select_ranges([0..0])
11377        });
11378    });
11379    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11380
11381    editor.update_in(cx, |editor, window, cx| {
11382        editor.handle_input("\n", window, cx)
11383    });
11384    cx.run_until_parked();
11385    save(&editor, &project, cx).await;
11386    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11387
11388    editor.update_in(cx, |editor, window, cx| {
11389        editor.undo(&Default::default(), window, cx);
11390    });
11391    save(&editor, &project, cx).await;
11392    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11393
11394    editor.update_in(cx, |editor, window, cx| {
11395        editor.redo(&Default::default(), window, cx);
11396    });
11397    cx.run_until_parked();
11398    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11399
11400    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11401        let save = editor
11402            .update_in(cx, |editor, window, cx| {
11403                editor.save(
11404                    SaveOptions {
11405                        format: true,
11406                        autosave: false,
11407                    },
11408                    project.clone(),
11409                    window,
11410                    cx,
11411                )
11412            })
11413            .unwrap();
11414        cx.executor().start_waiting();
11415        save.await;
11416        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11417    }
11418}
11419
11420#[gpui::test]
11421async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11422    init_test(cx, |_| {});
11423
11424    let cols = 4;
11425    let rows = 10;
11426    let sample_text_1 = sample_text(rows, cols, 'a');
11427    assert_eq!(
11428        sample_text_1,
11429        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11430    );
11431    let sample_text_2 = sample_text(rows, cols, 'l');
11432    assert_eq!(
11433        sample_text_2,
11434        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11435    );
11436    let sample_text_3 = sample_text(rows, cols, 'v');
11437    assert_eq!(
11438        sample_text_3,
11439        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11440    );
11441
11442    let fs = FakeFs::new(cx.executor());
11443    fs.insert_tree(
11444        path!("/a"),
11445        json!({
11446            "main.rs": sample_text_1,
11447            "other.rs": sample_text_2,
11448            "lib.rs": sample_text_3,
11449        }),
11450    )
11451    .await;
11452
11453    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11454    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11455    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11456
11457    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11458    language_registry.add(rust_lang());
11459    let mut fake_servers = language_registry.register_fake_lsp(
11460        "Rust",
11461        FakeLspAdapter {
11462            capabilities: lsp::ServerCapabilities {
11463                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11464                ..Default::default()
11465            },
11466            ..Default::default()
11467        },
11468    );
11469
11470    let worktree = project.update(cx, |project, cx| {
11471        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11472        assert_eq!(worktrees.len(), 1);
11473        worktrees.pop().unwrap()
11474    });
11475    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11476
11477    let buffer_1 = project
11478        .update(cx, |project, cx| {
11479            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11480        })
11481        .await
11482        .unwrap();
11483    let buffer_2 = project
11484        .update(cx, |project, cx| {
11485            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11486        })
11487        .await
11488        .unwrap();
11489    let buffer_3 = project
11490        .update(cx, |project, cx| {
11491            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11492        })
11493        .await
11494        .unwrap();
11495
11496    let multi_buffer = cx.new(|cx| {
11497        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11498        multi_buffer.push_excerpts(
11499            buffer_1.clone(),
11500            [
11501                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11502                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11503                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11504            ],
11505            cx,
11506        );
11507        multi_buffer.push_excerpts(
11508            buffer_2.clone(),
11509            [
11510                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11511                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11512                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11513            ],
11514            cx,
11515        );
11516        multi_buffer.push_excerpts(
11517            buffer_3.clone(),
11518            [
11519                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11520                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11521                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11522            ],
11523            cx,
11524        );
11525        multi_buffer
11526    });
11527    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11528        Editor::new(
11529            EditorMode::full(),
11530            multi_buffer,
11531            Some(project.clone()),
11532            window,
11533            cx,
11534        )
11535    });
11536
11537    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11538        editor.change_selections(
11539            SelectionEffects::scroll(Autoscroll::Next),
11540            window,
11541            cx,
11542            |s| s.select_ranges(Some(1..2)),
11543        );
11544        editor.insert("|one|two|three|", window, cx);
11545    });
11546    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11547    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11548        editor.change_selections(
11549            SelectionEffects::scroll(Autoscroll::Next),
11550            window,
11551            cx,
11552            |s| s.select_ranges(Some(60..70)),
11553        );
11554        editor.insert("|four|five|six|", window, cx);
11555    });
11556    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11557
11558    // First two buffers should be edited, but not the third one.
11559    assert_eq!(
11560        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11561        "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}",
11562    );
11563    buffer_1.update(cx, |buffer, _| {
11564        assert!(buffer.is_dirty());
11565        assert_eq!(
11566            buffer.text(),
11567            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11568        )
11569    });
11570    buffer_2.update(cx, |buffer, _| {
11571        assert!(buffer.is_dirty());
11572        assert_eq!(
11573            buffer.text(),
11574            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11575        )
11576    });
11577    buffer_3.update(cx, |buffer, _| {
11578        assert!(!buffer.is_dirty());
11579        assert_eq!(buffer.text(), sample_text_3,)
11580    });
11581    cx.executor().run_until_parked();
11582
11583    cx.executor().start_waiting();
11584    let save = multi_buffer_editor
11585        .update_in(cx, |editor, window, cx| {
11586            editor.save(
11587                SaveOptions {
11588                    format: true,
11589                    autosave: false,
11590                },
11591                project.clone(),
11592                window,
11593                cx,
11594            )
11595        })
11596        .unwrap();
11597
11598    let fake_server = fake_servers.next().await.unwrap();
11599    fake_server
11600        .server
11601        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11602            Ok(Some(vec![lsp::TextEdit::new(
11603                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11604                format!("[{} formatted]", params.text_document.uri),
11605            )]))
11606        })
11607        .detach();
11608    save.await;
11609
11610    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11611    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11612    assert_eq!(
11613        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11614        uri!(
11615            "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}"
11616        ),
11617    );
11618    buffer_1.update(cx, |buffer, _| {
11619        assert!(!buffer.is_dirty());
11620        assert_eq!(
11621            buffer.text(),
11622            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11623        )
11624    });
11625    buffer_2.update(cx, |buffer, _| {
11626        assert!(!buffer.is_dirty());
11627        assert_eq!(
11628            buffer.text(),
11629            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11630        )
11631    });
11632    buffer_3.update(cx, |buffer, _| {
11633        assert!(!buffer.is_dirty());
11634        assert_eq!(buffer.text(), sample_text_3,)
11635    });
11636}
11637
11638#[gpui::test]
11639async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11640    init_test(cx, |_| {});
11641
11642    let fs = FakeFs::new(cx.executor());
11643    fs.insert_tree(
11644        path!("/dir"),
11645        json!({
11646            "file1.rs": "fn main() { println!(\"hello\"); }",
11647            "file2.rs": "fn test() { println!(\"test\"); }",
11648            "file3.rs": "fn other() { println!(\"other\"); }\n",
11649        }),
11650    )
11651    .await;
11652
11653    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11654    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11655    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11656
11657    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11658    language_registry.add(rust_lang());
11659
11660    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11661    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11662
11663    // Open three buffers
11664    let buffer_1 = project
11665        .update(cx, |project, cx| {
11666            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11667        })
11668        .await
11669        .unwrap();
11670    let buffer_2 = project
11671        .update(cx, |project, cx| {
11672            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11673        })
11674        .await
11675        .unwrap();
11676    let buffer_3 = project
11677        .update(cx, |project, cx| {
11678            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11679        })
11680        .await
11681        .unwrap();
11682
11683    // Create a multi-buffer with all three buffers
11684    let multi_buffer = cx.new(|cx| {
11685        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11686        multi_buffer.push_excerpts(
11687            buffer_1.clone(),
11688            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11689            cx,
11690        );
11691        multi_buffer.push_excerpts(
11692            buffer_2.clone(),
11693            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11694            cx,
11695        );
11696        multi_buffer.push_excerpts(
11697            buffer_3.clone(),
11698            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11699            cx,
11700        );
11701        multi_buffer
11702    });
11703
11704    let editor = cx.new_window_entity(|window, cx| {
11705        Editor::new(
11706            EditorMode::full(),
11707            multi_buffer,
11708            Some(project.clone()),
11709            window,
11710            cx,
11711        )
11712    });
11713
11714    // Edit only the first buffer
11715    editor.update_in(cx, |editor, window, cx| {
11716        editor.change_selections(
11717            SelectionEffects::scroll(Autoscroll::Next),
11718            window,
11719            cx,
11720            |s| s.select_ranges(Some(10..10)),
11721        );
11722        editor.insert("// edited", window, cx);
11723    });
11724
11725    // Verify that only buffer 1 is dirty
11726    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11727    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11728    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11729
11730    // Get write counts after file creation (files were created with initial content)
11731    // We expect each file to have been written once during creation
11732    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11733    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11734    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11735
11736    // Perform autosave
11737    let save_task = editor.update_in(cx, |editor, window, cx| {
11738        editor.save(
11739            SaveOptions {
11740                format: true,
11741                autosave: true,
11742            },
11743            project.clone(),
11744            window,
11745            cx,
11746        )
11747    });
11748    save_task.await.unwrap();
11749
11750    // Only the dirty buffer should have been saved
11751    assert_eq!(
11752        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11753        1,
11754        "Buffer 1 was dirty, so it should have been written once during autosave"
11755    );
11756    assert_eq!(
11757        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11758        0,
11759        "Buffer 2 was clean, so it should not have been written during autosave"
11760    );
11761    assert_eq!(
11762        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11763        0,
11764        "Buffer 3 was clean, so it should not have been written during autosave"
11765    );
11766
11767    // Verify buffer states after autosave
11768    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11769    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11770    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11771
11772    // Now perform a manual save (format = true)
11773    let save_task = editor.update_in(cx, |editor, window, cx| {
11774        editor.save(
11775            SaveOptions {
11776                format: true,
11777                autosave: false,
11778            },
11779            project.clone(),
11780            window,
11781            cx,
11782        )
11783    });
11784    save_task.await.unwrap();
11785
11786    // During manual save, clean buffers don't get written to disk
11787    // They just get did_save called for language server notifications
11788    assert_eq!(
11789        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11790        1,
11791        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11792    );
11793    assert_eq!(
11794        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11795        0,
11796        "Buffer 2 should not have been written at all"
11797    );
11798    assert_eq!(
11799        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11800        0,
11801        "Buffer 3 should not have been written at all"
11802    );
11803}
11804
11805async fn setup_range_format_test(
11806    cx: &mut TestAppContext,
11807) -> (
11808    Entity<Project>,
11809    Entity<Editor>,
11810    &mut gpui::VisualTestContext,
11811    lsp::FakeLanguageServer,
11812) {
11813    init_test(cx, |_| {});
11814
11815    let fs = FakeFs::new(cx.executor());
11816    fs.insert_file(path!("/file.rs"), Default::default()).await;
11817
11818    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11819
11820    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11821    language_registry.add(rust_lang());
11822    let mut fake_servers = language_registry.register_fake_lsp(
11823        "Rust",
11824        FakeLspAdapter {
11825            capabilities: lsp::ServerCapabilities {
11826                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11827                ..lsp::ServerCapabilities::default()
11828            },
11829            ..FakeLspAdapter::default()
11830        },
11831    );
11832
11833    let buffer = project
11834        .update(cx, |project, cx| {
11835            project.open_local_buffer(path!("/file.rs"), cx)
11836        })
11837        .await
11838        .unwrap();
11839
11840    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11841    let (editor, cx) = cx.add_window_view(|window, cx| {
11842        build_editor_with_project(project.clone(), buffer, window, cx)
11843    });
11844
11845    cx.executor().start_waiting();
11846    let fake_server = fake_servers.next().await.unwrap();
11847
11848    (project, editor, cx, fake_server)
11849}
11850
11851#[gpui::test]
11852async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11853    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11854
11855    editor.update_in(cx, |editor, window, cx| {
11856        editor.set_text("one\ntwo\nthree\n", window, cx)
11857    });
11858    assert!(cx.read(|cx| editor.is_dirty(cx)));
11859
11860    let save = editor
11861        .update_in(cx, |editor, window, cx| {
11862            editor.save(
11863                SaveOptions {
11864                    format: true,
11865                    autosave: false,
11866                },
11867                project.clone(),
11868                window,
11869                cx,
11870            )
11871        })
11872        .unwrap();
11873    fake_server
11874        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11875            assert_eq!(
11876                params.text_document.uri,
11877                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11878            );
11879            assert_eq!(params.options.tab_size, 4);
11880            Ok(Some(vec![lsp::TextEdit::new(
11881                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11882                ", ".to_string(),
11883            )]))
11884        })
11885        .next()
11886        .await;
11887    cx.executor().start_waiting();
11888    save.await;
11889    assert_eq!(
11890        editor.update(cx, |editor, cx| editor.text(cx)),
11891        "one, two\nthree\n"
11892    );
11893    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11894}
11895
11896#[gpui::test]
11897async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11898    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11899
11900    editor.update_in(cx, |editor, window, cx| {
11901        editor.set_text("one\ntwo\nthree\n", window, cx)
11902    });
11903    assert!(cx.read(|cx| editor.is_dirty(cx)));
11904
11905    // Test that save still works when formatting hangs
11906    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11907        move |params, _| async move {
11908            assert_eq!(
11909                params.text_document.uri,
11910                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11911            );
11912            futures::future::pending::<()>().await;
11913            unreachable!()
11914        },
11915    );
11916    let save = editor
11917        .update_in(cx, |editor, window, cx| {
11918            editor.save(
11919                SaveOptions {
11920                    format: true,
11921                    autosave: false,
11922                },
11923                project.clone(),
11924                window,
11925                cx,
11926            )
11927        })
11928        .unwrap();
11929    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11930    cx.executor().start_waiting();
11931    save.await;
11932    assert_eq!(
11933        editor.update(cx, |editor, cx| editor.text(cx)),
11934        "one\ntwo\nthree\n"
11935    );
11936    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11937}
11938
11939#[gpui::test]
11940async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11941    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11942
11943    // Buffer starts clean, no formatting should be requested
11944    let save = editor
11945        .update_in(cx, |editor, window, cx| {
11946            editor.save(
11947                SaveOptions {
11948                    format: false,
11949                    autosave: false,
11950                },
11951                project.clone(),
11952                window,
11953                cx,
11954            )
11955        })
11956        .unwrap();
11957    let _pending_format_request = fake_server
11958        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11959            panic!("Should not be invoked");
11960        })
11961        .next();
11962    cx.executor().start_waiting();
11963    save.await;
11964    cx.run_until_parked();
11965}
11966
11967#[gpui::test]
11968async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11969    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11970
11971    // Set Rust language override and assert overridden tabsize is sent to language server
11972    update_test_language_settings(cx, |settings| {
11973        settings.languages.0.insert(
11974            "Rust".into(),
11975            LanguageSettingsContent {
11976                tab_size: NonZeroU32::new(8),
11977                ..Default::default()
11978            },
11979        );
11980    });
11981
11982    editor.update_in(cx, |editor, window, cx| {
11983        editor.set_text("something_new\n", window, cx)
11984    });
11985    assert!(cx.read(|cx| editor.is_dirty(cx)));
11986    let save = editor
11987        .update_in(cx, |editor, window, cx| {
11988            editor.save(
11989                SaveOptions {
11990                    format: true,
11991                    autosave: false,
11992                },
11993                project.clone(),
11994                window,
11995                cx,
11996            )
11997        })
11998        .unwrap();
11999    fake_server
12000        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12001            assert_eq!(
12002                params.text_document.uri,
12003                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12004            );
12005            assert_eq!(params.options.tab_size, 8);
12006            Ok(Some(Vec::new()))
12007        })
12008        .next()
12009        .await;
12010    save.await;
12011}
12012
12013#[gpui::test]
12014async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12015    init_test(cx, |settings| {
12016        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12017            settings::LanguageServerFormatterSpecifier::Current,
12018        )))
12019    });
12020
12021    let fs = FakeFs::new(cx.executor());
12022    fs.insert_file(path!("/file.rs"), Default::default()).await;
12023
12024    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12025
12026    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12027    language_registry.add(Arc::new(Language::new(
12028        LanguageConfig {
12029            name: "Rust".into(),
12030            matcher: LanguageMatcher {
12031                path_suffixes: vec!["rs".to_string()],
12032                ..Default::default()
12033            },
12034            ..LanguageConfig::default()
12035        },
12036        Some(tree_sitter_rust::LANGUAGE.into()),
12037    )));
12038    update_test_language_settings(cx, |settings| {
12039        // Enable Prettier formatting for the same buffer, and ensure
12040        // LSP is called instead of Prettier.
12041        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12042    });
12043    let mut fake_servers = language_registry.register_fake_lsp(
12044        "Rust",
12045        FakeLspAdapter {
12046            capabilities: lsp::ServerCapabilities {
12047                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12048                ..Default::default()
12049            },
12050            ..Default::default()
12051        },
12052    );
12053
12054    let buffer = project
12055        .update(cx, |project, cx| {
12056            project.open_local_buffer(path!("/file.rs"), cx)
12057        })
12058        .await
12059        .unwrap();
12060
12061    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12062    let (editor, cx) = cx.add_window_view(|window, cx| {
12063        build_editor_with_project(project.clone(), buffer, window, cx)
12064    });
12065    editor.update_in(cx, |editor, window, cx| {
12066        editor.set_text("one\ntwo\nthree\n", window, cx)
12067    });
12068
12069    cx.executor().start_waiting();
12070    let fake_server = fake_servers.next().await.unwrap();
12071
12072    let format = editor
12073        .update_in(cx, |editor, window, cx| {
12074            editor.perform_format(
12075                project.clone(),
12076                FormatTrigger::Manual,
12077                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12078                window,
12079                cx,
12080            )
12081        })
12082        .unwrap();
12083    fake_server
12084        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12085            assert_eq!(
12086                params.text_document.uri,
12087                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12088            );
12089            assert_eq!(params.options.tab_size, 4);
12090            Ok(Some(vec![lsp::TextEdit::new(
12091                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12092                ", ".to_string(),
12093            )]))
12094        })
12095        .next()
12096        .await;
12097    cx.executor().start_waiting();
12098    format.await;
12099    assert_eq!(
12100        editor.update(cx, |editor, cx| editor.text(cx)),
12101        "one, two\nthree\n"
12102    );
12103
12104    editor.update_in(cx, |editor, window, cx| {
12105        editor.set_text("one\ntwo\nthree\n", window, cx)
12106    });
12107    // Ensure we don't lock if formatting hangs.
12108    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12109        move |params, _| async move {
12110            assert_eq!(
12111                params.text_document.uri,
12112                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12113            );
12114            futures::future::pending::<()>().await;
12115            unreachable!()
12116        },
12117    );
12118    let format = editor
12119        .update_in(cx, |editor, window, cx| {
12120            editor.perform_format(
12121                project,
12122                FormatTrigger::Manual,
12123                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12124                window,
12125                cx,
12126            )
12127        })
12128        .unwrap();
12129    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12130    cx.executor().start_waiting();
12131    format.await;
12132    assert_eq!(
12133        editor.update(cx, |editor, cx| editor.text(cx)),
12134        "one\ntwo\nthree\n"
12135    );
12136}
12137
12138#[gpui::test]
12139async fn test_multiple_formatters(cx: &mut TestAppContext) {
12140    init_test(cx, |settings| {
12141        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12142        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12143            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12144            Formatter::CodeAction("code-action-1".into()),
12145            Formatter::CodeAction("code-action-2".into()),
12146        ]))
12147    });
12148
12149    let fs = FakeFs::new(cx.executor());
12150    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12151        .await;
12152
12153    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12154    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12155    language_registry.add(rust_lang());
12156
12157    let mut fake_servers = language_registry.register_fake_lsp(
12158        "Rust",
12159        FakeLspAdapter {
12160            capabilities: lsp::ServerCapabilities {
12161                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12162                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12163                    commands: vec!["the-command-for-code-action-1".into()],
12164                    ..Default::default()
12165                }),
12166                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12167                ..Default::default()
12168            },
12169            ..Default::default()
12170        },
12171    );
12172
12173    let buffer = project
12174        .update(cx, |project, cx| {
12175            project.open_local_buffer(path!("/file.rs"), cx)
12176        })
12177        .await
12178        .unwrap();
12179
12180    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12181    let (editor, cx) = cx.add_window_view(|window, cx| {
12182        build_editor_with_project(project.clone(), buffer, window, cx)
12183    });
12184
12185    cx.executor().start_waiting();
12186
12187    let fake_server = fake_servers.next().await.unwrap();
12188    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12189        move |_params, _| async move {
12190            Ok(Some(vec![lsp::TextEdit::new(
12191                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12192                "applied-formatting\n".to_string(),
12193            )]))
12194        },
12195    );
12196    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12197        move |params, _| async move {
12198            let requested_code_actions = params.context.only.expect("Expected code action request");
12199            assert_eq!(requested_code_actions.len(), 1);
12200
12201            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12202            let code_action = match requested_code_actions[0].as_str() {
12203                "code-action-1" => lsp::CodeAction {
12204                    kind: Some("code-action-1".into()),
12205                    edit: Some(lsp::WorkspaceEdit::new(
12206                        [(
12207                            uri,
12208                            vec![lsp::TextEdit::new(
12209                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12210                                "applied-code-action-1-edit\n".to_string(),
12211                            )],
12212                        )]
12213                        .into_iter()
12214                        .collect(),
12215                    )),
12216                    command: Some(lsp::Command {
12217                        command: "the-command-for-code-action-1".into(),
12218                        ..Default::default()
12219                    }),
12220                    ..Default::default()
12221                },
12222                "code-action-2" => lsp::CodeAction {
12223                    kind: Some("code-action-2".into()),
12224                    edit: Some(lsp::WorkspaceEdit::new(
12225                        [(
12226                            uri,
12227                            vec![lsp::TextEdit::new(
12228                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12229                                "applied-code-action-2-edit\n".to_string(),
12230                            )],
12231                        )]
12232                        .into_iter()
12233                        .collect(),
12234                    )),
12235                    ..Default::default()
12236                },
12237                req => panic!("Unexpected code action request: {:?}", req),
12238            };
12239            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12240                code_action,
12241            )]))
12242        },
12243    );
12244
12245    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12246        move |params, _| async move { Ok(params) }
12247    });
12248
12249    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12250    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12251        let fake = fake_server.clone();
12252        let lock = command_lock.clone();
12253        move |params, _| {
12254            assert_eq!(params.command, "the-command-for-code-action-1");
12255            let fake = fake.clone();
12256            let lock = lock.clone();
12257            async move {
12258                lock.lock().await;
12259                fake.server
12260                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12261                        label: None,
12262                        edit: lsp::WorkspaceEdit {
12263                            changes: Some(
12264                                [(
12265                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12266                                    vec![lsp::TextEdit {
12267                                        range: lsp::Range::new(
12268                                            lsp::Position::new(0, 0),
12269                                            lsp::Position::new(0, 0),
12270                                        ),
12271                                        new_text: "applied-code-action-1-command\n".into(),
12272                                    }],
12273                                )]
12274                                .into_iter()
12275                                .collect(),
12276                            ),
12277                            ..Default::default()
12278                        },
12279                    })
12280                    .await
12281                    .into_response()
12282                    .unwrap();
12283                Ok(Some(json!(null)))
12284            }
12285        }
12286    });
12287
12288    cx.executor().start_waiting();
12289    editor
12290        .update_in(cx, |editor, window, cx| {
12291            editor.perform_format(
12292                project.clone(),
12293                FormatTrigger::Manual,
12294                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12295                window,
12296                cx,
12297            )
12298        })
12299        .unwrap()
12300        .await;
12301    editor.update(cx, |editor, cx| {
12302        assert_eq!(
12303            editor.text(cx),
12304            r#"
12305                applied-code-action-2-edit
12306                applied-code-action-1-command
12307                applied-code-action-1-edit
12308                applied-formatting
12309                one
12310                two
12311                three
12312            "#
12313            .unindent()
12314        );
12315    });
12316
12317    editor.update_in(cx, |editor, window, cx| {
12318        editor.undo(&Default::default(), window, cx);
12319        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12320    });
12321
12322    // Perform a manual edit while waiting for an LSP command
12323    // that's being run as part of a formatting code action.
12324    let lock_guard = command_lock.lock().await;
12325    let format = editor
12326        .update_in(cx, |editor, window, cx| {
12327            editor.perform_format(
12328                project.clone(),
12329                FormatTrigger::Manual,
12330                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12331                window,
12332                cx,
12333            )
12334        })
12335        .unwrap();
12336    cx.run_until_parked();
12337    editor.update(cx, |editor, cx| {
12338        assert_eq!(
12339            editor.text(cx),
12340            r#"
12341                applied-code-action-1-edit
12342                applied-formatting
12343                one
12344                two
12345                three
12346            "#
12347            .unindent()
12348        );
12349
12350        editor.buffer.update(cx, |buffer, cx| {
12351            let ix = buffer.len(cx);
12352            buffer.edit([(ix..ix, "edited\n")], None, cx);
12353        });
12354    });
12355
12356    // Allow the LSP command to proceed. Because the buffer was edited,
12357    // the second code action will not be run.
12358    drop(lock_guard);
12359    format.await;
12360    editor.update_in(cx, |editor, window, cx| {
12361        assert_eq!(
12362            editor.text(cx),
12363            r#"
12364                applied-code-action-1-command
12365                applied-code-action-1-edit
12366                applied-formatting
12367                one
12368                two
12369                three
12370                edited
12371            "#
12372            .unindent()
12373        );
12374
12375        // The manual edit is undone first, because it is the last thing the user did
12376        // (even though the command completed afterwards).
12377        editor.undo(&Default::default(), window, cx);
12378        assert_eq!(
12379            editor.text(cx),
12380            r#"
12381                applied-code-action-1-command
12382                applied-code-action-1-edit
12383                applied-formatting
12384                one
12385                two
12386                three
12387            "#
12388            .unindent()
12389        );
12390
12391        // All the formatting (including the command, which completed after the manual edit)
12392        // is undone together.
12393        editor.undo(&Default::default(), window, cx);
12394        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12395    });
12396}
12397
12398#[gpui::test]
12399async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12400    init_test(cx, |settings| {
12401        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12402            settings::LanguageServerFormatterSpecifier::Current,
12403        )]))
12404    });
12405
12406    let fs = FakeFs::new(cx.executor());
12407    fs.insert_file(path!("/file.ts"), Default::default()).await;
12408
12409    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12410
12411    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12412    language_registry.add(Arc::new(Language::new(
12413        LanguageConfig {
12414            name: "TypeScript".into(),
12415            matcher: LanguageMatcher {
12416                path_suffixes: vec!["ts".to_string()],
12417                ..Default::default()
12418            },
12419            ..LanguageConfig::default()
12420        },
12421        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12422    )));
12423    update_test_language_settings(cx, |settings| {
12424        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12425    });
12426    let mut fake_servers = language_registry.register_fake_lsp(
12427        "TypeScript",
12428        FakeLspAdapter {
12429            capabilities: lsp::ServerCapabilities {
12430                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12431                ..Default::default()
12432            },
12433            ..Default::default()
12434        },
12435    );
12436
12437    let buffer = project
12438        .update(cx, |project, cx| {
12439            project.open_local_buffer(path!("/file.ts"), cx)
12440        })
12441        .await
12442        .unwrap();
12443
12444    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12445    let (editor, cx) = cx.add_window_view(|window, cx| {
12446        build_editor_with_project(project.clone(), buffer, window, cx)
12447    });
12448    editor.update_in(cx, |editor, window, cx| {
12449        editor.set_text(
12450            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12451            window,
12452            cx,
12453        )
12454    });
12455
12456    cx.executor().start_waiting();
12457    let fake_server = fake_servers.next().await.unwrap();
12458
12459    let format = editor
12460        .update_in(cx, |editor, window, cx| {
12461            editor.perform_code_action_kind(
12462                project.clone(),
12463                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12464                window,
12465                cx,
12466            )
12467        })
12468        .unwrap();
12469    fake_server
12470        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12471            assert_eq!(
12472                params.text_document.uri,
12473                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12474            );
12475            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12476                lsp::CodeAction {
12477                    title: "Organize Imports".to_string(),
12478                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12479                    edit: Some(lsp::WorkspaceEdit {
12480                        changes: Some(
12481                            [(
12482                                params.text_document.uri.clone(),
12483                                vec![lsp::TextEdit::new(
12484                                    lsp::Range::new(
12485                                        lsp::Position::new(1, 0),
12486                                        lsp::Position::new(2, 0),
12487                                    ),
12488                                    "".to_string(),
12489                                )],
12490                            )]
12491                            .into_iter()
12492                            .collect(),
12493                        ),
12494                        ..Default::default()
12495                    }),
12496                    ..Default::default()
12497                },
12498            )]))
12499        })
12500        .next()
12501        .await;
12502    cx.executor().start_waiting();
12503    format.await;
12504    assert_eq!(
12505        editor.update(cx, |editor, cx| editor.text(cx)),
12506        "import { a } from 'module';\n\nconst x = a;\n"
12507    );
12508
12509    editor.update_in(cx, |editor, window, cx| {
12510        editor.set_text(
12511            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12512            window,
12513            cx,
12514        )
12515    });
12516    // Ensure we don't lock if code action hangs.
12517    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12518        move |params, _| async move {
12519            assert_eq!(
12520                params.text_document.uri,
12521                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12522            );
12523            futures::future::pending::<()>().await;
12524            unreachable!()
12525        },
12526    );
12527    let format = editor
12528        .update_in(cx, |editor, window, cx| {
12529            editor.perform_code_action_kind(
12530                project,
12531                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12532                window,
12533                cx,
12534            )
12535        })
12536        .unwrap();
12537    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12538    cx.executor().start_waiting();
12539    format.await;
12540    assert_eq!(
12541        editor.update(cx, |editor, cx| editor.text(cx)),
12542        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12543    );
12544}
12545
12546#[gpui::test]
12547async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12548    init_test(cx, |_| {});
12549
12550    let mut cx = EditorLspTestContext::new_rust(
12551        lsp::ServerCapabilities {
12552            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12553            ..Default::default()
12554        },
12555        cx,
12556    )
12557    .await;
12558
12559    cx.set_state(indoc! {"
12560        one.twoˇ
12561    "});
12562
12563    // The format request takes a long time. When it completes, it inserts
12564    // a newline and an indent before the `.`
12565    cx.lsp
12566        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12567            let executor = cx.background_executor().clone();
12568            async move {
12569                executor.timer(Duration::from_millis(100)).await;
12570                Ok(Some(vec![lsp::TextEdit {
12571                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12572                    new_text: "\n    ".into(),
12573                }]))
12574            }
12575        });
12576
12577    // Submit a format request.
12578    let format_1 = cx
12579        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12580        .unwrap();
12581    cx.executor().run_until_parked();
12582
12583    // Submit a second format request.
12584    let format_2 = cx
12585        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12586        .unwrap();
12587    cx.executor().run_until_parked();
12588
12589    // Wait for both format requests to complete
12590    cx.executor().advance_clock(Duration::from_millis(200));
12591    cx.executor().start_waiting();
12592    format_1.await.unwrap();
12593    cx.executor().start_waiting();
12594    format_2.await.unwrap();
12595
12596    // The formatting edits only happens once.
12597    cx.assert_editor_state(indoc! {"
12598        one
12599            .twoˇ
12600    "});
12601}
12602
12603#[gpui::test]
12604async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12605    init_test(cx, |settings| {
12606        settings.defaults.formatter = Some(FormatterList::default())
12607    });
12608
12609    let mut cx = EditorLspTestContext::new_rust(
12610        lsp::ServerCapabilities {
12611            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12612            ..Default::default()
12613        },
12614        cx,
12615    )
12616    .await;
12617
12618    // Record which buffer changes have been sent to the language server
12619    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12620    cx.lsp
12621        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12622            let buffer_changes = buffer_changes.clone();
12623            move |params, _| {
12624                buffer_changes.lock().extend(
12625                    params
12626                        .content_changes
12627                        .into_iter()
12628                        .map(|e| (e.range.unwrap(), e.text)),
12629                );
12630            }
12631        });
12632    // Handle formatting requests to the language server.
12633    cx.lsp
12634        .set_request_handler::<lsp::request::Formatting, _, _>({
12635            let buffer_changes = buffer_changes.clone();
12636            move |_, _| {
12637                let buffer_changes = buffer_changes.clone();
12638                // Insert blank lines between each line of the buffer.
12639                async move {
12640                    // When formatting is requested, trailing whitespace has already been stripped,
12641                    // and the trailing newline has already been added.
12642                    assert_eq!(
12643                        &buffer_changes.lock()[1..],
12644                        &[
12645                            (
12646                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12647                                "".into()
12648                            ),
12649                            (
12650                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12651                                "".into()
12652                            ),
12653                            (
12654                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12655                                "\n".into()
12656                            ),
12657                        ]
12658                    );
12659
12660                    Ok(Some(vec![
12661                        lsp::TextEdit {
12662                            range: lsp::Range::new(
12663                                lsp::Position::new(1, 0),
12664                                lsp::Position::new(1, 0),
12665                            ),
12666                            new_text: "\n".into(),
12667                        },
12668                        lsp::TextEdit {
12669                            range: lsp::Range::new(
12670                                lsp::Position::new(2, 0),
12671                                lsp::Position::new(2, 0),
12672                            ),
12673                            new_text: "\n".into(),
12674                        },
12675                    ]))
12676                }
12677            }
12678        });
12679
12680    // Set up a buffer white some trailing whitespace and no trailing newline.
12681    cx.set_state(
12682        &[
12683            "one ",   //
12684            "twoˇ",   //
12685            "three ", //
12686            "four",   //
12687        ]
12688        .join("\n"),
12689    );
12690    cx.run_until_parked();
12691
12692    // Submit a format request.
12693    let format = cx
12694        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12695        .unwrap();
12696
12697    cx.run_until_parked();
12698    // After formatting the buffer, the trailing whitespace is stripped,
12699    // a newline is appended, and the edits provided by the language server
12700    // have been applied.
12701    format.await.unwrap();
12702
12703    cx.assert_editor_state(
12704        &[
12705            "one",   //
12706            "",      //
12707            "twoˇ",  //
12708            "",      //
12709            "three", //
12710            "four",  //
12711            "",      //
12712        ]
12713        .join("\n"),
12714    );
12715
12716    // Undoing the formatting undoes the trailing whitespace removal, the
12717    // trailing newline, and the LSP edits.
12718    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12719    cx.assert_editor_state(
12720        &[
12721            "one ",   //
12722            "twoˇ",   //
12723            "three ", //
12724            "four",   //
12725        ]
12726        .join("\n"),
12727    );
12728}
12729
12730#[gpui::test]
12731async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12732    cx: &mut TestAppContext,
12733) {
12734    init_test(cx, |_| {});
12735
12736    cx.update(|cx| {
12737        cx.update_global::<SettingsStore, _>(|settings, cx| {
12738            settings.update_user_settings(cx, |settings| {
12739                settings.editor.auto_signature_help = Some(true);
12740            });
12741        });
12742    });
12743
12744    let mut cx = EditorLspTestContext::new_rust(
12745        lsp::ServerCapabilities {
12746            signature_help_provider: Some(lsp::SignatureHelpOptions {
12747                ..Default::default()
12748            }),
12749            ..Default::default()
12750        },
12751        cx,
12752    )
12753    .await;
12754
12755    let language = Language::new(
12756        LanguageConfig {
12757            name: "Rust".into(),
12758            brackets: BracketPairConfig {
12759                pairs: vec![
12760                    BracketPair {
12761                        start: "{".to_string(),
12762                        end: "}".to_string(),
12763                        close: true,
12764                        surround: true,
12765                        newline: true,
12766                    },
12767                    BracketPair {
12768                        start: "(".to_string(),
12769                        end: ")".to_string(),
12770                        close: true,
12771                        surround: true,
12772                        newline: true,
12773                    },
12774                    BracketPair {
12775                        start: "/*".to_string(),
12776                        end: " */".to_string(),
12777                        close: true,
12778                        surround: true,
12779                        newline: true,
12780                    },
12781                    BracketPair {
12782                        start: "[".to_string(),
12783                        end: "]".to_string(),
12784                        close: false,
12785                        surround: false,
12786                        newline: true,
12787                    },
12788                    BracketPair {
12789                        start: "\"".to_string(),
12790                        end: "\"".to_string(),
12791                        close: true,
12792                        surround: true,
12793                        newline: false,
12794                    },
12795                    BracketPair {
12796                        start: "<".to_string(),
12797                        end: ">".to_string(),
12798                        close: false,
12799                        surround: true,
12800                        newline: true,
12801                    },
12802                ],
12803                ..Default::default()
12804            },
12805            autoclose_before: "})]".to_string(),
12806            ..Default::default()
12807        },
12808        Some(tree_sitter_rust::LANGUAGE.into()),
12809    );
12810    let language = Arc::new(language);
12811
12812    cx.language_registry().add(language.clone());
12813    cx.update_buffer(|buffer, cx| {
12814        buffer.set_language(Some(language), cx);
12815    });
12816
12817    cx.set_state(
12818        &r#"
12819            fn main() {
12820                sampleˇ
12821            }
12822        "#
12823        .unindent(),
12824    );
12825
12826    cx.update_editor(|editor, window, cx| {
12827        editor.handle_input("(", window, cx);
12828    });
12829    cx.assert_editor_state(
12830        &"
12831            fn main() {
12832                sample(ˇ)
12833            }
12834        "
12835        .unindent(),
12836    );
12837
12838    let mocked_response = lsp::SignatureHelp {
12839        signatures: vec![lsp::SignatureInformation {
12840            label: "fn sample(param1: u8, param2: u8)".to_string(),
12841            documentation: None,
12842            parameters: Some(vec![
12843                lsp::ParameterInformation {
12844                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12845                    documentation: None,
12846                },
12847                lsp::ParameterInformation {
12848                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12849                    documentation: None,
12850                },
12851            ]),
12852            active_parameter: None,
12853        }],
12854        active_signature: Some(0),
12855        active_parameter: Some(0),
12856    };
12857    handle_signature_help_request(&mut cx, mocked_response).await;
12858
12859    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12860        .await;
12861
12862    cx.editor(|editor, _, _| {
12863        let signature_help_state = editor.signature_help_state.popover().cloned();
12864        let signature = signature_help_state.unwrap();
12865        assert_eq!(
12866            signature.signatures[signature.current_signature].label,
12867            "fn sample(param1: u8, param2: u8)"
12868        );
12869    });
12870}
12871
12872#[gpui::test]
12873async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12874    init_test(cx, |_| {});
12875
12876    cx.update(|cx| {
12877        cx.update_global::<SettingsStore, _>(|settings, cx| {
12878            settings.update_user_settings(cx, |settings| {
12879                settings.editor.auto_signature_help = Some(false);
12880                settings.editor.show_signature_help_after_edits = Some(false);
12881            });
12882        });
12883    });
12884
12885    let mut cx = EditorLspTestContext::new_rust(
12886        lsp::ServerCapabilities {
12887            signature_help_provider: Some(lsp::SignatureHelpOptions {
12888                ..Default::default()
12889            }),
12890            ..Default::default()
12891        },
12892        cx,
12893    )
12894    .await;
12895
12896    let language = Language::new(
12897        LanguageConfig {
12898            name: "Rust".into(),
12899            brackets: BracketPairConfig {
12900                pairs: vec![
12901                    BracketPair {
12902                        start: "{".to_string(),
12903                        end: "}".to_string(),
12904                        close: true,
12905                        surround: true,
12906                        newline: true,
12907                    },
12908                    BracketPair {
12909                        start: "(".to_string(),
12910                        end: ")".to_string(),
12911                        close: true,
12912                        surround: true,
12913                        newline: true,
12914                    },
12915                    BracketPair {
12916                        start: "/*".to_string(),
12917                        end: " */".to_string(),
12918                        close: true,
12919                        surround: true,
12920                        newline: true,
12921                    },
12922                    BracketPair {
12923                        start: "[".to_string(),
12924                        end: "]".to_string(),
12925                        close: false,
12926                        surround: false,
12927                        newline: true,
12928                    },
12929                    BracketPair {
12930                        start: "\"".to_string(),
12931                        end: "\"".to_string(),
12932                        close: true,
12933                        surround: true,
12934                        newline: false,
12935                    },
12936                    BracketPair {
12937                        start: "<".to_string(),
12938                        end: ">".to_string(),
12939                        close: false,
12940                        surround: true,
12941                        newline: true,
12942                    },
12943                ],
12944                ..Default::default()
12945            },
12946            autoclose_before: "})]".to_string(),
12947            ..Default::default()
12948        },
12949        Some(tree_sitter_rust::LANGUAGE.into()),
12950    );
12951    let language = Arc::new(language);
12952
12953    cx.language_registry().add(language.clone());
12954    cx.update_buffer(|buffer, cx| {
12955        buffer.set_language(Some(language), cx);
12956    });
12957
12958    // Ensure that signature_help is not called when no signature help is enabled.
12959    cx.set_state(
12960        &r#"
12961            fn main() {
12962                sampleˇ
12963            }
12964        "#
12965        .unindent(),
12966    );
12967    cx.update_editor(|editor, window, cx| {
12968        editor.handle_input("(", window, cx);
12969    });
12970    cx.assert_editor_state(
12971        &"
12972            fn main() {
12973                sample(ˇ)
12974            }
12975        "
12976        .unindent(),
12977    );
12978    cx.editor(|editor, _, _| {
12979        assert!(editor.signature_help_state.task().is_none());
12980    });
12981
12982    let mocked_response = lsp::SignatureHelp {
12983        signatures: vec![lsp::SignatureInformation {
12984            label: "fn sample(param1: u8, param2: u8)".to_string(),
12985            documentation: None,
12986            parameters: Some(vec![
12987                lsp::ParameterInformation {
12988                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12989                    documentation: None,
12990                },
12991                lsp::ParameterInformation {
12992                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12993                    documentation: None,
12994                },
12995            ]),
12996            active_parameter: None,
12997        }],
12998        active_signature: Some(0),
12999        active_parameter: Some(0),
13000    };
13001
13002    // Ensure that signature_help is called when enabled afte edits
13003    cx.update(|_, cx| {
13004        cx.update_global::<SettingsStore, _>(|settings, cx| {
13005            settings.update_user_settings(cx, |settings| {
13006                settings.editor.auto_signature_help = Some(false);
13007                settings.editor.show_signature_help_after_edits = Some(true);
13008            });
13009        });
13010    });
13011    cx.set_state(
13012        &r#"
13013            fn main() {
13014                sampleˇ
13015            }
13016        "#
13017        .unindent(),
13018    );
13019    cx.update_editor(|editor, window, cx| {
13020        editor.handle_input("(", window, cx);
13021    });
13022    cx.assert_editor_state(
13023        &"
13024            fn main() {
13025                sample(ˇ)
13026            }
13027        "
13028        .unindent(),
13029    );
13030    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13031    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13032        .await;
13033    cx.update_editor(|editor, _, _| {
13034        let signature_help_state = editor.signature_help_state.popover().cloned();
13035        assert!(signature_help_state.is_some());
13036        let signature = signature_help_state.unwrap();
13037        assert_eq!(
13038            signature.signatures[signature.current_signature].label,
13039            "fn sample(param1: u8, param2: u8)"
13040        );
13041        editor.signature_help_state = SignatureHelpState::default();
13042    });
13043
13044    // Ensure that signature_help is called when auto signature help override is enabled
13045    cx.update(|_, cx| {
13046        cx.update_global::<SettingsStore, _>(|settings, cx| {
13047            settings.update_user_settings(cx, |settings| {
13048                settings.editor.auto_signature_help = Some(true);
13049                settings.editor.show_signature_help_after_edits = Some(false);
13050            });
13051        });
13052    });
13053    cx.set_state(
13054        &r#"
13055            fn main() {
13056                sampleˇ
13057            }
13058        "#
13059        .unindent(),
13060    );
13061    cx.update_editor(|editor, window, cx| {
13062        editor.handle_input("(", window, cx);
13063    });
13064    cx.assert_editor_state(
13065        &"
13066            fn main() {
13067                sample(ˇ)
13068            }
13069        "
13070        .unindent(),
13071    );
13072    handle_signature_help_request(&mut cx, mocked_response).await;
13073    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13074        .await;
13075    cx.editor(|editor, _, _| {
13076        let signature_help_state = editor.signature_help_state.popover().cloned();
13077        assert!(signature_help_state.is_some());
13078        let signature = signature_help_state.unwrap();
13079        assert_eq!(
13080            signature.signatures[signature.current_signature].label,
13081            "fn sample(param1: u8, param2: u8)"
13082        );
13083    });
13084}
13085
13086#[gpui::test]
13087async fn test_signature_help(cx: &mut TestAppContext) {
13088    init_test(cx, |_| {});
13089    cx.update(|cx| {
13090        cx.update_global::<SettingsStore, _>(|settings, cx| {
13091            settings.update_user_settings(cx, |settings| {
13092                settings.editor.auto_signature_help = Some(true);
13093            });
13094        });
13095    });
13096
13097    let mut cx = EditorLspTestContext::new_rust(
13098        lsp::ServerCapabilities {
13099            signature_help_provider: Some(lsp::SignatureHelpOptions {
13100                ..Default::default()
13101            }),
13102            ..Default::default()
13103        },
13104        cx,
13105    )
13106    .await;
13107
13108    // A test that directly calls `show_signature_help`
13109    cx.update_editor(|editor, window, cx| {
13110        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13111    });
13112
13113    let mocked_response = lsp::SignatureHelp {
13114        signatures: vec![lsp::SignatureInformation {
13115            label: "fn sample(param1: u8, param2: u8)".to_string(),
13116            documentation: None,
13117            parameters: Some(vec![
13118                lsp::ParameterInformation {
13119                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13120                    documentation: None,
13121                },
13122                lsp::ParameterInformation {
13123                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13124                    documentation: None,
13125                },
13126            ]),
13127            active_parameter: None,
13128        }],
13129        active_signature: Some(0),
13130        active_parameter: Some(0),
13131    };
13132    handle_signature_help_request(&mut cx, mocked_response).await;
13133
13134    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13135        .await;
13136
13137    cx.editor(|editor, _, _| {
13138        let signature_help_state = editor.signature_help_state.popover().cloned();
13139        assert!(signature_help_state.is_some());
13140        let signature = signature_help_state.unwrap();
13141        assert_eq!(
13142            signature.signatures[signature.current_signature].label,
13143            "fn sample(param1: u8, param2: u8)"
13144        );
13145    });
13146
13147    // When exiting outside from inside the brackets, `signature_help` is closed.
13148    cx.set_state(indoc! {"
13149        fn main() {
13150            sample(ˇ);
13151        }
13152
13153        fn sample(param1: u8, param2: u8) {}
13154    "});
13155
13156    cx.update_editor(|editor, window, cx| {
13157        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13158            s.select_ranges([0..0])
13159        });
13160    });
13161
13162    let mocked_response = lsp::SignatureHelp {
13163        signatures: Vec::new(),
13164        active_signature: None,
13165        active_parameter: None,
13166    };
13167    handle_signature_help_request(&mut cx, mocked_response).await;
13168
13169    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13170        .await;
13171
13172    cx.editor(|editor, _, _| {
13173        assert!(!editor.signature_help_state.is_shown());
13174    });
13175
13176    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13177    cx.set_state(indoc! {"
13178        fn main() {
13179            sample(ˇ);
13180        }
13181
13182        fn sample(param1: u8, param2: u8) {}
13183    "});
13184
13185    let mocked_response = lsp::SignatureHelp {
13186        signatures: vec![lsp::SignatureInformation {
13187            label: "fn sample(param1: u8, param2: u8)".to_string(),
13188            documentation: None,
13189            parameters: Some(vec![
13190                lsp::ParameterInformation {
13191                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13192                    documentation: None,
13193                },
13194                lsp::ParameterInformation {
13195                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13196                    documentation: None,
13197                },
13198            ]),
13199            active_parameter: None,
13200        }],
13201        active_signature: Some(0),
13202        active_parameter: Some(0),
13203    };
13204    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13205    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13206        .await;
13207    cx.editor(|editor, _, _| {
13208        assert!(editor.signature_help_state.is_shown());
13209    });
13210
13211    // Restore the popover with more parameter input
13212    cx.set_state(indoc! {"
13213        fn main() {
13214            sample(param1, param2ˇ);
13215        }
13216
13217        fn sample(param1: u8, param2: u8) {}
13218    "});
13219
13220    let mocked_response = lsp::SignatureHelp {
13221        signatures: vec![lsp::SignatureInformation {
13222            label: "fn sample(param1: u8, param2: u8)".to_string(),
13223            documentation: None,
13224            parameters: Some(vec![
13225                lsp::ParameterInformation {
13226                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13227                    documentation: None,
13228                },
13229                lsp::ParameterInformation {
13230                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13231                    documentation: None,
13232                },
13233            ]),
13234            active_parameter: None,
13235        }],
13236        active_signature: Some(0),
13237        active_parameter: Some(1),
13238    };
13239    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13240    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13241        .await;
13242
13243    // When selecting a range, the popover is gone.
13244    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13245    cx.update_editor(|editor, window, cx| {
13246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13247            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13248        })
13249    });
13250    cx.assert_editor_state(indoc! {"
13251        fn main() {
13252            sample(param1, «ˇparam2»);
13253        }
13254
13255        fn sample(param1: u8, param2: u8) {}
13256    "});
13257    cx.editor(|editor, _, _| {
13258        assert!(!editor.signature_help_state.is_shown());
13259    });
13260
13261    // When unselecting again, the popover is back if within the brackets.
13262    cx.update_editor(|editor, window, cx| {
13263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13264            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13265        })
13266    });
13267    cx.assert_editor_state(indoc! {"
13268        fn main() {
13269            sample(param1, ˇparam2);
13270        }
13271
13272        fn sample(param1: u8, param2: u8) {}
13273    "});
13274    handle_signature_help_request(&mut cx, mocked_response).await;
13275    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13276        .await;
13277    cx.editor(|editor, _, _| {
13278        assert!(editor.signature_help_state.is_shown());
13279    });
13280
13281    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13282    cx.update_editor(|editor, window, cx| {
13283        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13284            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13285            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13286        })
13287    });
13288    cx.assert_editor_state(indoc! {"
13289        fn main() {
13290            sample(param1, ˇparam2);
13291        }
13292
13293        fn sample(param1: u8, param2: u8) {}
13294    "});
13295
13296    let mocked_response = lsp::SignatureHelp {
13297        signatures: vec![lsp::SignatureInformation {
13298            label: "fn sample(param1: u8, param2: u8)".to_string(),
13299            documentation: None,
13300            parameters: Some(vec![
13301                lsp::ParameterInformation {
13302                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13303                    documentation: None,
13304                },
13305                lsp::ParameterInformation {
13306                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13307                    documentation: None,
13308                },
13309            ]),
13310            active_parameter: None,
13311        }],
13312        active_signature: Some(0),
13313        active_parameter: Some(1),
13314    };
13315    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13316    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13317        .await;
13318    cx.update_editor(|editor, _, cx| {
13319        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13320    });
13321    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13322        .await;
13323    cx.update_editor(|editor, window, cx| {
13324        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13325            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13326        })
13327    });
13328    cx.assert_editor_state(indoc! {"
13329        fn main() {
13330            sample(param1, «ˇparam2»);
13331        }
13332
13333        fn sample(param1: u8, param2: u8) {}
13334    "});
13335    cx.update_editor(|editor, window, cx| {
13336        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13337            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13338        })
13339    });
13340    cx.assert_editor_state(indoc! {"
13341        fn main() {
13342            sample(param1, ˇparam2);
13343        }
13344
13345        fn sample(param1: u8, param2: u8) {}
13346    "});
13347    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13348        .await;
13349}
13350
13351#[gpui::test]
13352async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13353    init_test(cx, |_| {});
13354
13355    let mut cx = EditorLspTestContext::new_rust(
13356        lsp::ServerCapabilities {
13357            signature_help_provider: Some(lsp::SignatureHelpOptions {
13358                ..Default::default()
13359            }),
13360            ..Default::default()
13361        },
13362        cx,
13363    )
13364    .await;
13365
13366    cx.set_state(indoc! {"
13367        fn main() {
13368            overloadedˇ
13369        }
13370    "});
13371
13372    cx.update_editor(|editor, window, cx| {
13373        editor.handle_input("(", window, cx);
13374        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13375    });
13376
13377    // Mock response with 3 signatures
13378    let mocked_response = lsp::SignatureHelp {
13379        signatures: vec![
13380            lsp::SignatureInformation {
13381                label: "fn overloaded(x: i32)".to_string(),
13382                documentation: None,
13383                parameters: Some(vec![lsp::ParameterInformation {
13384                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13385                    documentation: None,
13386                }]),
13387                active_parameter: None,
13388            },
13389            lsp::SignatureInformation {
13390                label: "fn overloaded(x: i32, y: i32)".to_string(),
13391                documentation: None,
13392                parameters: Some(vec![
13393                    lsp::ParameterInformation {
13394                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13395                        documentation: None,
13396                    },
13397                    lsp::ParameterInformation {
13398                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13399                        documentation: None,
13400                    },
13401                ]),
13402                active_parameter: None,
13403            },
13404            lsp::SignatureInformation {
13405                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13406                documentation: None,
13407                parameters: Some(vec![
13408                    lsp::ParameterInformation {
13409                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13410                        documentation: None,
13411                    },
13412                    lsp::ParameterInformation {
13413                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13414                        documentation: None,
13415                    },
13416                    lsp::ParameterInformation {
13417                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13418                        documentation: None,
13419                    },
13420                ]),
13421                active_parameter: None,
13422            },
13423        ],
13424        active_signature: Some(1),
13425        active_parameter: Some(0),
13426    };
13427    handle_signature_help_request(&mut cx, mocked_response).await;
13428
13429    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13430        .await;
13431
13432    // Verify we have multiple signatures and the right one is selected
13433    cx.editor(|editor, _, _| {
13434        let popover = editor.signature_help_state.popover().cloned().unwrap();
13435        assert_eq!(popover.signatures.len(), 3);
13436        // active_signature was 1, so that should be the current
13437        assert_eq!(popover.current_signature, 1);
13438        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13439        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13440        assert_eq!(
13441            popover.signatures[2].label,
13442            "fn overloaded(x: i32, y: i32, z: i32)"
13443        );
13444    });
13445
13446    // Test navigation functionality
13447    cx.update_editor(|editor, window, cx| {
13448        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13449    });
13450
13451    cx.editor(|editor, _, _| {
13452        let popover = editor.signature_help_state.popover().cloned().unwrap();
13453        assert_eq!(popover.current_signature, 2);
13454    });
13455
13456    // Test wrap around
13457    cx.update_editor(|editor, window, cx| {
13458        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13459    });
13460
13461    cx.editor(|editor, _, _| {
13462        let popover = editor.signature_help_state.popover().cloned().unwrap();
13463        assert_eq!(popover.current_signature, 0);
13464    });
13465
13466    // Test previous navigation
13467    cx.update_editor(|editor, window, cx| {
13468        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13469    });
13470
13471    cx.editor(|editor, _, _| {
13472        let popover = editor.signature_help_state.popover().cloned().unwrap();
13473        assert_eq!(popover.current_signature, 2);
13474    });
13475}
13476
13477#[gpui::test]
13478async fn test_completion_mode(cx: &mut TestAppContext) {
13479    init_test(cx, |_| {});
13480    let mut cx = EditorLspTestContext::new_rust(
13481        lsp::ServerCapabilities {
13482            completion_provider: Some(lsp::CompletionOptions {
13483                resolve_provider: Some(true),
13484                ..Default::default()
13485            }),
13486            ..Default::default()
13487        },
13488        cx,
13489    )
13490    .await;
13491
13492    struct Run {
13493        run_description: &'static str,
13494        initial_state: String,
13495        buffer_marked_text: String,
13496        completion_label: &'static str,
13497        completion_text: &'static str,
13498        expected_with_insert_mode: String,
13499        expected_with_replace_mode: String,
13500        expected_with_replace_subsequence_mode: String,
13501        expected_with_replace_suffix_mode: String,
13502    }
13503
13504    let runs = [
13505        Run {
13506            run_description: "Start of word matches completion text",
13507            initial_state: "before ediˇ after".into(),
13508            buffer_marked_text: "before <edi|> after".into(),
13509            completion_label: "editor",
13510            completion_text: "editor",
13511            expected_with_insert_mode: "before editorˇ after".into(),
13512            expected_with_replace_mode: "before editorˇ after".into(),
13513            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13514            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13515        },
13516        Run {
13517            run_description: "Accept same text at the middle of the word",
13518            initial_state: "before ediˇtor after".into(),
13519            buffer_marked_text: "before <edi|tor> after".into(),
13520            completion_label: "editor",
13521            completion_text: "editor",
13522            expected_with_insert_mode: "before editorˇtor after".into(),
13523            expected_with_replace_mode: "before editorˇ after".into(),
13524            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13525            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13526        },
13527        Run {
13528            run_description: "End of word matches completion text -- cursor at end",
13529            initial_state: "before torˇ after".into(),
13530            buffer_marked_text: "before <tor|> after".into(),
13531            completion_label: "editor",
13532            completion_text: "editor",
13533            expected_with_insert_mode: "before editorˇ after".into(),
13534            expected_with_replace_mode: "before editorˇ after".into(),
13535            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13536            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13537        },
13538        Run {
13539            run_description: "End of word matches completion text -- cursor at start",
13540            initial_state: "before ˇtor after".into(),
13541            buffer_marked_text: "before <|tor> after".into(),
13542            completion_label: "editor",
13543            completion_text: "editor",
13544            expected_with_insert_mode: "before editorˇtor after".into(),
13545            expected_with_replace_mode: "before editorˇ after".into(),
13546            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13547            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13548        },
13549        Run {
13550            run_description: "Prepend text containing whitespace",
13551            initial_state: "pˇfield: bool".into(),
13552            buffer_marked_text: "<p|field>: bool".into(),
13553            completion_label: "pub ",
13554            completion_text: "pub ",
13555            expected_with_insert_mode: "pub ˇfield: bool".into(),
13556            expected_with_replace_mode: "pub ˇ: bool".into(),
13557            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13558            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13559        },
13560        Run {
13561            run_description: "Add element to start of list",
13562            initial_state: "[element_ˇelement_2]".into(),
13563            buffer_marked_text: "[<element_|element_2>]".into(),
13564            completion_label: "element_1",
13565            completion_text: "element_1",
13566            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13567            expected_with_replace_mode: "[element_1ˇ]".into(),
13568            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13569            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13570        },
13571        Run {
13572            run_description: "Add element to start of list -- first and second elements are equal",
13573            initial_state: "[elˇelement]".into(),
13574            buffer_marked_text: "[<el|element>]".into(),
13575            completion_label: "element",
13576            completion_text: "element",
13577            expected_with_insert_mode: "[elementˇelement]".into(),
13578            expected_with_replace_mode: "[elementˇ]".into(),
13579            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13580            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13581        },
13582        Run {
13583            run_description: "Ends with matching suffix",
13584            initial_state: "SubˇError".into(),
13585            buffer_marked_text: "<Sub|Error>".into(),
13586            completion_label: "SubscriptionError",
13587            completion_text: "SubscriptionError",
13588            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13589            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13590            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13591            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13592        },
13593        Run {
13594            run_description: "Suffix is a subsequence -- contiguous",
13595            initial_state: "SubˇErr".into(),
13596            buffer_marked_text: "<Sub|Err>".into(),
13597            completion_label: "SubscriptionError",
13598            completion_text: "SubscriptionError",
13599            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13600            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13601            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13602            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13603        },
13604        Run {
13605            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13606            initial_state: "Suˇscrirr".into(),
13607            buffer_marked_text: "<Su|scrirr>".into(),
13608            completion_label: "SubscriptionError",
13609            completion_text: "SubscriptionError",
13610            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13611            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13612            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13613            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13614        },
13615        Run {
13616            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13617            initial_state: "foo(indˇix)".into(),
13618            buffer_marked_text: "foo(<ind|ix>)".into(),
13619            completion_label: "node_index",
13620            completion_text: "node_index",
13621            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13622            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13623            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13624            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13625        },
13626        Run {
13627            run_description: "Replace range ends before cursor - should extend to cursor",
13628            initial_state: "before editˇo after".into(),
13629            buffer_marked_text: "before <{ed}>it|o after".into(),
13630            completion_label: "editor",
13631            completion_text: "editor",
13632            expected_with_insert_mode: "before editorˇo after".into(),
13633            expected_with_replace_mode: "before editorˇo after".into(),
13634            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13635            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13636        },
13637        Run {
13638            run_description: "Uses label for suffix matching",
13639            initial_state: "before ediˇtor after".into(),
13640            buffer_marked_text: "before <edi|tor> after".into(),
13641            completion_label: "editor",
13642            completion_text: "editor()",
13643            expected_with_insert_mode: "before editor()ˇtor after".into(),
13644            expected_with_replace_mode: "before editor()ˇ after".into(),
13645            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13646            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13647        },
13648        Run {
13649            run_description: "Case insensitive subsequence and suffix matching",
13650            initial_state: "before EDiˇtoR after".into(),
13651            buffer_marked_text: "before <EDi|toR> after".into(),
13652            completion_label: "editor",
13653            completion_text: "editor",
13654            expected_with_insert_mode: "before editorˇtoR after".into(),
13655            expected_with_replace_mode: "before editorˇ after".into(),
13656            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13657            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13658        },
13659    ];
13660
13661    for run in runs {
13662        let run_variations = [
13663            (LspInsertMode::Insert, run.expected_with_insert_mode),
13664            (LspInsertMode::Replace, run.expected_with_replace_mode),
13665            (
13666                LspInsertMode::ReplaceSubsequence,
13667                run.expected_with_replace_subsequence_mode,
13668            ),
13669            (
13670                LspInsertMode::ReplaceSuffix,
13671                run.expected_with_replace_suffix_mode,
13672            ),
13673        ];
13674
13675        for (lsp_insert_mode, expected_text) in run_variations {
13676            eprintln!(
13677                "run = {:?}, mode = {lsp_insert_mode:.?}",
13678                run.run_description,
13679            );
13680
13681            update_test_language_settings(&mut cx, |settings| {
13682                settings.defaults.completions = Some(CompletionSettingsContent {
13683                    lsp_insert_mode: Some(lsp_insert_mode),
13684                    words: Some(WordsCompletionMode::Disabled),
13685                    words_min_length: Some(0),
13686                    ..Default::default()
13687                });
13688            });
13689
13690            cx.set_state(&run.initial_state);
13691            cx.update_editor(|editor, window, cx| {
13692                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13693            });
13694
13695            let counter = Arc::new(AtomicUsize::new(0));
13696            handle_completion_request_with_insert_and_replace(
13697                &mut cx,
13698                &run.buffer_marked_text,
13699                vec![(run.completion_label, run.completion_text)],
13700                counter.clone(),
13701            )
13702            .await;
13703            cx.condition(|editor, _| editor.context_menu_visible())
13704                .await;
13705            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13706
13707            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13708                editor
13709                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13710                    .unwrap()
13711            });
13712            cx.assert_editor_state(&expected_text);
13713            handle_resolve_completion_request(&mut cx, None).await;
13714            apply_additional_edits.await.unwrap();
13715        }
13716    }
13717}
13718
13719#[gpui::test]
13720async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13721    init_test(cx, |_| {});
13722    let mut cx = EditorLspTestContext::new_rust(
13723        lsp::ServerCapabilities {
13724            completion_provider: Some(lsp::CompletionOptions {
13725                resolve_provider: Some(true),
13726                ..Default::default()
13727            }),
13728            ..Default::default()
13729        },
13730        cx,
13731    )
13732    .await;
13733
13734    let initial_state = "SubˇError";
13735    let buffer_marked_text = "<Sub|Error>";
13736    let completion_text = "SubscriptionError";
13737    let expected_with_insert_mode = "SubscriptionErrorˇError";
13738    let expected_with_replace_mode = "SubscriptionErrorˇ";
13739
13740    update_test_language_settings(&mut cx, |settings| {
13741        settings.defaults.completions = Some(CompletionSettingsContent {
13742            words: Some(WordsCompletionMode::Disabled),
13743            words_min_length: Some(0),
13744            // set the opposite here to ensure that the action is overriding the default behavior
13745            lsp_insert_mode: Some(LspInsertMode::Insert),
13746            ..Default::default()
13747        });
13748    });
13749
13750    cx.set_state(initial_state);
13751    cx.update_editor(|editor, window, cx| {
13752        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13753    });
13754
13755    let counter = Arc::new(AtomicUsize::new(0));
13756    handle_completion_request_with_insert_and_replace(
13757        &mut cx,
13758        buffer_marked_text,
13759        vec![(completion_text, completion_text)],
13760        counter.clone(),
13761    )
13762    .await;
13763    cx.condition(|editor, _| editor.context_menu_visible())
13764        .await;
13765    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13766
13767    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13768        editor
13769            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13770            .unwrap()
13771    });
13772    cx.assert_editor_state(expected_with_replace_mode);
13773    handle_resolve_completion_request(&mut cx, None).await;
13774    apply_additional_edits.await.unwrap();
13775
13776    update_test_language_settings(&mut cx, |settings| {
13777        settings.defaults.completions = Some(CompletionSettingsContent {
13778            words: Some(WordsCompletionMode::Disabled),
13779            words_min_length: Some(0),
13780            // set the opposite here to ensure that the action is overriding the default behavior
13781            lsp_insert_mode: Some(LspInsertMode::Replace),
13782            ..Default::default()
13783        });
13784    });
13785
13786    cx.set_state(initial_state);
13787    cx.update_editor(|editor, window, cx| {
13788        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13789    });
13790    handle_completion_request_with_insert_and_replace(
13791        &mut cx,
13792        buffer_marked_text,
13793        vec![(completion_text, completion_text)],
13794        counter.clone(),
13795    )
13796    .await;
13797    cx.condition(|editor, _| editor.context_menu_visible())
13798        .await;
13799    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13800
13801    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13802        editor
13803            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13804            .unwrap()
13805    });
13806    cx.assert_editor_state(expected_with_insert_mode);
13807    handle_resolve_completion_request(&mut cx, None).await;
13808    apply_additional_edits.await.unwrap();
13809}
13810
13811#[gpui::test]
13812async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13813    init_test(cx, |_| {});
13814    let mut cx = EditorLspTestContext::new_rust(
13815        lsp::ServerCapabilities {
13816            completion_provider: Some(lsp::CompletionOptions {
13817                resolve_provider: Some(true),
13818                ..Default::default()
13819            }),
13820            ..Default::default()
13821        },
13822        cx,
13823    )
13824    .await;
13825
13826    // scenario: surrounding text matches completion text
13827    let completion_text = "to_offset";
13828    let initial_state = indoc! {"
13829        1. buf.to_offˇsuffix
13830        2. buf.to_offˇsuf
13831        3. buf.to_offˇfix
13832        4. buf.to_offˇ
13833        5. into_offˇensive
13834        6. ˇsuffix
13835        7. let ˇ //
13836        8. aaˇzz
13837        9. buf.to_off«zzzzzˇ»suffix
13838        10. buf.«ˇzzzzz»suffix
13839        11. to_off«ˇzzzzz»
13840
13841        buf.to_offˇsuffix  // newest cursor
13842    "};
13843    let completion_marked_buffer = indoc! {"
13844        1. buf.to_offsuffix
13845        2. buf.to_offsuf
13846        3. buf.to_offfix
13847        4. buf.to_off
13848        5. into_offensive
13849        6. suffix
13850        7. let  //
13851        8. aazz
13852        9. buf.to_offzzzzzsuffix
13853        10. buf.zzzzzsuffix
13854        11. to_offzzzzz
13855
13856        buf.<to_off|suffix>  // newest cursor
13857    "};
13858    let expected = indoc! {"
13859        1. buf.to_offsetˇ
13860        2. buf.to_offsetˇsuf
13861        3. buf.to_offsetˇfix
13862        4. buf.to_offsetˇ
13863        5. into_offsetˇensive
13864        6. to_offsetˇsuffix
13865        7. let to_offsetˇ //
13866        8. aato_offsetˇzz
13867        9. buf.to_offsetˇ
13868        10. buf.to_offsetˇsuffix
13869        11. to_offsetˇ
13870
13871        buf.to_offsetˇ  // newest cursor
13872    "};
13873    cx.set_state(initial_state);
13874    cx.update_editor(|editor, window, cx| {
13875        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13876    });
13877    handle_completion_request_with_insert_and_replace(
13878        &mut cx,
13879        completion_marked_buffer,
13880        vec![(completion_text, completion_text)],
13881        Arc::new(AtomicUsize::new(0)),
13882    )
13883    .await;
13884    cx.condition(|editor, _| editor.context_menu_visible())
13885        .await;
13886    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13887        editor
13888            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13889            .unwrap()
13890    });
13891    cx.assert_editor_state(expected);
13892    handle_resolve_completion_request(&mut cx, None).await;
13893    apply_additional_edits.await.unwrap();
13894
13895    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13896    let completion_text = "foo_and_bar";
13897    let initial_state = indoc! {"
13898        1. ooanbˇ
13899        2. zooanbˇ
13900        3. ooanbˇz
13901        4. zooanbˇz
13902        5. ooanˇ
13903        6. oanbˇ
13904
13905        ooanbˇ
13906    "};
13907    let completion_marked_buffer = indoc! {"
13908        1. ooanb
13909        2. zooanb
13910        3. ooanbz
13911        4. zooanbz
13912        5. ooan
13913        6. oanb
13914
13915        <ooanb|>
13916    "};
13917    let expected = indoc! {"
13918        1. foo_and_barˇ
13919        2. zfoo_and_barˇ
13920        3. foo_and_barˇz
13921        4. zfoo_and_barˇz
13922        5. ooanfoo_and_barˇ
13923        6. oanbfoo_and_barˇ
13924
13925        foo_and_barˇ
13926    "};
13927    cx.set_state(initial_state);
13928    cx.update_editor(|editor, window, cx| {
13929        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13930    });
13931    handle_completion_request_with_insert_and_replace(
13932        &mut cx,
13933        completion_marked_buffer,
13934        vec![(completion_text, completion_text)],
13935        Arc::new(AtomicUsize::new(0)),
13936    )
13937    .await;
13938    cx.condition(|editor, _| editor.context_menu_visible())
13939        .await;
13940    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13941        editor
13942            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13943            .unwrap()
13944    });
13945    cx.assert_editor_state(expected);
13946    handle_resolve_completion_request(&mut cx, None).await;
13947    apply_additional_edits.await.unwrap();
13948
13949    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13950    // (expects the same as if it was inserted at the end)
13951    let completion_text = "foo_and_bar";
13952    let initial_state = indoc! {"
13953        1. ooˇanb
13954        2. zooˇanb
13955        3. ooˇanbz
13956        4. zooˇanbz
13957
13958        ooˇanb
13959    "};
13960    let completion_marked_buffer = indoc! {"
13961        1. ooanb
13962        2. zooanb
13963        3. ooanbz
13964        4. zooanbz
13965
13966        <oo|anb>
13967    "};
13968    let expected = indoc! {"
13969        1. foo_and_barˇ
13970        2. zfoo_and_barˇ
13971        3. foo_and_barˇz
13972        4. zfoo_and_barˇz
13973
13974        foo_and_barˇ
13975    "};
13976    cx.set_state(initial_state);
13977    cx.update_editor(|editor, window, cx| {
13978        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13979    });
13980    handle_completion_request_with_insert_and_replace(
13981        &mut cx,
13982        completion_marked_buffer,
13983        vec![(completion_text, completion_text)],
13984        Arc::new(AtomicUsize::new(0)),
13985    )
13986    .await;
13987    cx.condition(|editor, _| editor.context_menu_visible())
13988        .await;
13989    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13990        editor
13991            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13992            .unwrap()
13993    });
13994    cx.assert_editor_state(expected);
13995    handle_resolve_completion_request(&mut cx, None).await;
13996    apply_additional_edits.await.unwrap();
13997}
13998
13999// This used to crash
14000#[gpui::test]
14001async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14002    init_test(cx, |_| {});
14003
14004    let buffer_text = indoc! {"
14005        fn main() {
14006            10.satu;
14007
14008            //
14009            // separate cursors so they open in different excerpts (manually reproducible)
14010            //
14011
14012            10.satu20;
14013        }
14014    "};
14015    let multibuffer_text_with_selections = indoc! {"
14016        fn main() {
14017            10.satuˇ;
14018
14019            //
14020
14021            //
14022
14023            10.satuˇ20;
14024        }
14025    "};
14026    let expected_multibuffer = indoc! {"
14027        fn main() {
14028            10.saturating_sub()ˇ;
14029
14030            //
14031
14032            //
14033
14034            10.saturating_sub()ˇ;
14035        }
14036    "};
14037
14038    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14039    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14040
14041    let fs = FakeFs::new(cx.executor());
14042    fs.insert_tree(
14043        path!("/a"),
14044        json!({
14045            "main.rs": buffer_text,
14046        }),
14047    )
14048    .await;
14049
14050    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14051    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14052    language_registry.add(rust_lang());
14053    let mut fake_servers = language_registry.register_fake_lsp(
14054        "Rust",
14055        FakeLspAdapter {
14056            capabilities: lsp::ServerCapabilities {
14057                completion_provider: Some(lsp::CompletionOptions {
14058                    resolve_provider: None,
14059                    ..lsp::CompletionOptions::default()
14060                }),
14061                ..lsp::ServerCapabilities::default()
14062            },
14063            ..FakeLspAdapter::default()
14064        },
14065    );
14066    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14067    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14068    let buffer = project
14069        .update(cx, |project, cx| {
14070            project.open_local_buffer(path!("/a/main.rs"), cx)
14071        })
14072        .await
14073        .unwrap();
14074
14075    let multi_buffer = cx.new(|cx| {
14076        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14077        multi_buffer.push_excerpts(
14078            buffer.clone(),
14079            [ExcerptRange::new(0..first_excerpt_end)],
14080            cx,
14081        );
14082        multi_buffer.push_excerpts(
14083            buffer.clone(),
14084            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14085            cx,
14086        );
14087        multi_buffer
14088    });
14089
14090    let editor = workspace
14091        .update(cx, |_, window, cx| {
14092            cx.new(|cx| {
14093                Editor::new(
14094                    EditorMode::Full {
14095                        scale_ui_elements_with_buffer_font_size: false,
14096                        show_active_line_background: false,
14097                        sized_by_content: false,
14098                    },
14099                    multi_buffer.clone(),
14100                    Some(project.clone()),
14101                    window,
14102                    cx,
14103                )
14104            })
14105        })
14106        .unwrap();
14107
14108    let pane = workspace
14109        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14110        .unwrap();
14111    pane.update_in(cx, |pane, window, cx| {
14112        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14113    });
14114
14115    let fake_server = fake_servers.next().await.unwrap();
14116
14117    editor.update_in(cx, |editor, window, cx| {
14118        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14119            s.select_ranges([
14120                Point::new(1, 11)..Point::new(1, 11),
14121                Point::new(7, 11)..Point::new(7, 11),
14122            ])
14123        });
14124
14125        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14126    });
14127
14128    editor.update_in(cx, |editor, window, cx| {
14129        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14130    });
14131
14132    fake_server
14133        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14134            let completion_item = lsp::CompletionItem {
14135                label: "saturating_sub()".into(),
14136                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14137                    lsp::InsertReplaceEdit {
14138                        new_text: "saturating_sub()".to_owned(),
14139                        insert: lsp::Range::new(
14140                            lsp::Position::new(7, 7),
14141                            lsp::Position::new(7, 11),
14142                        ),
14143                        replace: lsp::Range::new(
14144                            lsp::Position::new(7, 7),
14145                            lsp::Position::new(7, 13),
14146                        ),
14147                    },
14148                )),
14149                ..lsp::CompletionItem::default()
14150            };
14151
14152            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14153        })
14154        .next()
14155        .await
14156        .unwrap();
14157
14158    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14159        .await;
14160
14161    editor
14162        .update_in(cx, |editor, window, cx| {
14163            editor
14164                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14165                .unwrap()
14166        })
14167        .await
14168        .unwrap();
14169
14170    editor.update(cx, |editor, cx| {
14171        assert_text_with_selections(editor, expected_multibuffer, cx);
14172    })
14173}
14174
14175#[gpui::test]
14176async fn test_completion(cx: &mut TestAppContext) {
14177    init_test(cx, |_| {});
14178
14179    let mut cx = EditorLspTestContext::new_rust(
14180        lsp::ServerCapabilities {
14181            completion_provider: Some(lsp::CompletionOptions {
14182                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14183                resolve_provider: Some(true),
14184                ..Default::default()
14185            }),
14186            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14187            ..Default::default()
14188        },
14189        cx,
14190    )
14191    .await;
14192    let counter = Arc::new(AtomicUsize::new(0));
14193
14194    cx.set_state(indoc! {"
14195        oneˇ
14196        two
14197        three
14198    "});
14199    cx.simulate_keystroke(".");
14200    handle_completion_request(
14201        indoc! {"
14202            one.|<>
14203            two
14204            three
14205        "},
14206        vec!["first_completion", "second_completion"],
14207        true,
14208        counter.clone(),
14209        &mut cx,
14210    )
14211    .await;
14212    cx.condition(|editor, _| editor.context_menu_visible())
14213        .await;
14214    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14215
14216    let _handler = handle_signature_help_request(
14217        &mut cx,
14218        lsp::SignatureHelp {
14219            signatures: vec![lsp::SignatureInformation {
14220                label: "test signature".to_string(),
14221                documentation: None,
14222                parameters: Some(vec![lsp::ParameterInformation {
14223                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14224                    documentation: None,
14225                }]),
14226                active_parameter: None,
14227            }],
14228            active_signature: None,
14229            active_parameter: None,
14230        },
14231    );
14232    cx.update_editor(|editor, window, cx| {
14233        assert!(
14234            !editor.signature_help_state.is_shown(),
14235            "No signature help was called for"
14236        );
14237        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14238    });
14239    cx.run_until_parked();
14240    cx.update_editor(|editor, _, _| {
14241        assert!(
14242            !editor.signature_help_state.is_shown(),
14243            "No signature help should be shown when completions menu is open"
14244        );
14245    });
14246
14247    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14248        editor.context_menu_next(&Default::default(), window, cx);
14249        editor
14250            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14251            .unwrap()
14252    });
14253    cx.assert_editor_state(indoc! {"
14254        one.second_completionˇ
14255        two
14256        three
14257    "});
14258
14259    handle_resolve_completion_request(
14260        &mut cx,
14261        Some(vec![
14262            (
14263                //This overlaps with the primary completion edit which is
14264                //misbehavior from the LSP spec, test that we filter it out
14265                indoc! {"
14266                    one.second_ˇcompletion
14267                    two
14268                    threeˇ
14269                "},
14270                "overlapping additional edit",
14271            ),
14272            (
14273                indoc! {"
14274                    one.second_completion
14275                    two
14276                    threeˇ
14277                "},
14278                "\nadditional edit",
14279            ),
14280        ]),
14281    )
14282    .await;
14283    apply_additional_edits.await.unwrap();
14284    cx.assert_editor_state(indoc! {"
14285        one.second_completionˇ
14286        two
14287        three
14288        additional edit
14289    "});
14290
14291    cx.set_state(indoc! {"
14292        one.second_completion
14293        twoˇ
14294        threeˇ
14295        additional edit
14296    "});
14297    cx.simulate_keystroke(" ");
14298    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14299    cx.simulate_keystroke("s");
14300    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14301
14302    cx.assert_editor_state(indoc! {"
14303        one.second_completion
14304        two sˇ
14305        three sˇ
14306        additional edit
14307    "});
14308    handle_completion_request(
14309        indoc! {"
14310            one.second_completion
14311            two s
14312            three <s|>
14313            additional edit
14314        "},
14315        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14316        true,
14317        counter.clone(),
14318        &mut cx,
14319    )
14320    .await;
14321    cx.condition(|editor, _| editor.context_menu_visible())
14322        .await;
14323    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14324
14325    cx.simulate_keystroke("i");
14326
14327    handle_completion_request(
14328        indoc! {"
14329            one.second_completion
14330            two si
14331            three <si|>
14332            additional edit
14333        "},
14334        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14335        true,
14336        counter.clone(),
14337        &mut cx,
14338    )
14339    .await;
14340    cx.condition(|editor, _| editor.context_menu_visible())
14341        .await;
14342    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14343
14344    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14345        editor
14346            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14347            .unwrap()
14348    });
14349    cx.assert_editor_state(indoc! {"
14350        one.second_completion
14351        two sixth_completionˇ
14352        three sixth_completionˇ
14353        additional edit
14354    "});
14355
14356    apply_additional_edits.await.unwrap();
14357
14358    update_test_language_settings(&mut cx, |settings| {
14359        settings.defaults.show_completions_on_input = Some(false);
14360    });
14361    cx.set_state("editorˇ");
14362    cx.simulate_keystroke(".");
14363    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14364    cx.simulate_keystrokes("c l o");
14365    cx.assert_editor_state("editor.cloˇ");
14366    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14367    cx.update_editor(|editor, window, cx| {
14368        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14369    });
14370    handle_completion_request(
14371        "editor.<clo|>",
14372        vec!["close", "clobber"],
14373        true,
14374        counter.clone(),
14375        &mut cx,
14376    )
14377    .await;
14378    cx.condition(|editor, _| editor.context_menu_visible())
14379        .await;
14380    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14381
14382    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14383        editor
14384            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14385            .unwrap()
14386    });
14387    cx.assert_editor_state("editor.clobberˇ");
14388    handle_resolve_completion_request(&mut cx, None).await;
14389    apply_additional_edits.await.unwrap();
14390}
14391
14392#[gpui::test]
14393async fn test_completion_reuse(cx: &mut TestAppContext) {
14394    init_test(cx, |_| {});
14395
14396    let mut cx = EditorLspTestContext::new_rust(
14397        lsp::ServerCapabilities {
14398            completion_provider: Some(lsp::CompletionOptions {
14399                trigger_characters: Some(vec![".".to_string()]),
14400                ..Default::default()
14401            }),
14402            ..Default::default()
14403        },
14404        cx,
14405    )
14406    .await;
14407
14408    let counter = Arc::new(AtomicUsize::new(0));
14409    cx.set_state("objˇ");
14410    cx.simulate_keystroke(".");
14411
14412    // Initial completion request returns complete results
14413    let is_incomplete = false;
14414    handle_completion_request(
14415        "obj.|<>",
14416        vec!["a", "ab", "abc"],
14417        is_incomplete,
14418        counter.clone(),
14419        &mut cx,
14420    )
14421    .await;
14422    cx.run_until_parked();
14423    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14424    cx.assert_editor_state("obj.ˇ");
14425    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14426
14427    // Type "a" - filters existing completions
14428    cx.simulate_keystroke("a");
14429    cx.run_until_parked();
14430    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14431    cx.assert_editor_state("obj.aˇ");
14432    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14433
14434    // Type "b" - filters existing completions
14435    cx.simulate_keystroke("b");
14436    cx.run_until_parked();
14437    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14438    cx.assert_editor_state("obj.abˇ");
14439    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14440
14441    // Type "c" - filters existing completions
14442    cx.simulate_keystroke("c");
14443    cx.run_until_parked();
14444    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14445    cx.assert_editor_state("obj.abcˇ");
14446    check_displayed_completions(vec!["abc"], &mut cx);
14447
14448    // Backspace to delete "c" - filters existing completions
14449    cx.update_editor(|editor, window, cx| {
14450        editor.backspace(&Backspace, window, cx);
14451    });
14452    cx.run_until_parked();
14453    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14454    cx.assert_editor_state("obj.abˇ");
14455    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14456
14457    // Moving cursor to the left dismisses menu.
14458    cx.update_editor(|editor, window, cx| {
14459        editor.move_left(&MoveLeft, window, cx);
14460    });
14461    cx.run_until_parked();
14462    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14463    cx.assert_editor_state("obj.aˇb");
14464    cx.update_editor(|editor, _, _| {
14465        assert_eq!(editor.context_menu_visible(), false);
14466    });
14467
14468    // Type "b" - new request
14469    cx.simulate_keystroke("b");
14470    let is_incomplete = false;
14471    handle_completion_request(
14472        "obj.<ab|>a",
14473        vec!["ab", "abc"],
14474        is_incomplete,
14475        counter.clone(),
14476        &mut cx,
14477    )
14478    .await;
14479    cx.run_until_parked();
14480    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14481    cx.assert_editor_state("obj.abˇb");
14482    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14483
14484    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14485    cx.update_editor(|editor, window, cx| {
14486        editor.backspace(&Backspace, window, cx);
14487    });
14488    let is_incomplete = false;
14489    handle_completion_request(
14490        "obj.<a|>b",
14491        vec!["a", "ab", "abc"],
14492        is_incomplete,
14493        counter.clone(),
14494        &mut cx,
14495    )
14496    .await;
14497    cx.run_until_parked();
14498    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14499    cx.assert_editor_state("obj.aˇb");
14500    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14501
14502    // Backspace to delete "a" - dismisses menu.
14503    cx.update_editor(|editor, window, cx| {
14504        editor.backspace(&Backspace, window, cx);
14505    });
14506    cx.run_until_parked();
14507    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14508    cx.assert_editor_state("obj.ˇb");
14509    cx.update_editor(|editor, _, _| {
14510        assert_eq!(editor.context_menu_visible(), false);
14511    });
14512}
14513
14514#[gpui::test]
14515async fn test_word_completion(cx: &mut TestAppContext) {
14516    let lsp_fetch_timeout_ms = 10;
14517    init_test(cx, |language_settings| {
14518        language_settings.defaults.completions = Some(CompletionSettingsContent {
14519            words_min_length: Some(0),
14520            lsp_fetch_timeout_ms: Some(10),
14521            lsp_insert_mode: Some(LspInsertMode::Insert),
14522            ..Default::default()
14523        });
14524    });
14525
14526    let mut cx = EditorLspTestContext::new_rust(
14527        lsp::ServerCapabilities {
14528            completion_provider: Some(lsp::CompletionOptions {
14529                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14530                ..lsp::CompletionOptions::default()
14531            }),
14532            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14533            ..lsp::ServerCapabilities::default()
14534        },
14535        cx,
14536    )
14537    .await;
14538
14539    let throttle_completions = Arc::new(AtomicBool::new(false));
14540
14541    let lsp_throttle_completions = throttle_completions.clone();
14542    let _completion_requests_handler =
14543        cx.lsp
14544            .server
14545            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14546                let lsp_throttle_completions = lsp_throttle_completions.clone();
14547                let cx = cx.clone();
14548                async move {
14549                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14550                        cx.background_executor()
14551                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14552                            .await;
14553                    }
14554                    Ok(Some(lsp::CompletionResponse::Array(vec![
14555                        lsp::CompletionItem {
14556                            label: "first".into(),
14557                            ..lsp::CompletionItem::default()
14558                        },
14559                        lsp::CompletionItem {
14560                            label: "last".into(),
14561                            ..lsp::CompletionItem::default()
14562                        },
14563                    ])))
14564                }
14565            });
14566
14567    cx.set_state(indoc! {"
14568        oneˇ
14569        two
14570        three
14571    "});
14572    cx.simulate_keystroke(".");
14573    cx.executor().run_until_parked();
14574    cx.condition(|editor, _| editor.context_menu_visible())
14575        .await;
14576    cx.update_editor(|editor, window, cx| {
14577        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14578        {
14579            assert_eq!(
14580                completion_menu_entries(menu),
14581                &["first", "last"],
14582                "When LSP server is fast to reply, no fallback word completions are used"
14583            );
14584        } else {
14585            panic!("expected completion menu to be open");
14586        }
14587        editor.cancel(&Cancel, window, cx);
14588    });
14589    cx.executor().run_until_parked();
14590    cx.condition(|editor, _| !editor.context_menu_visible())
14591        .await;
14592
14593    throttle_completions.store(true, atomic::Ordering::Release);
14594    cx.simulate_keystroke(".");
14595    cx.executor()
14596        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14597    cx.executor().run_until_parked();
14598    cx.condition(|editor, _| editor.context_menu_visible())
14599        .await;
14600    cx.update_editor(|editor, _, _| {
14601        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14602        {
14603            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14604                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14605        } else {
14606            panic!("expected completion menu to be open");
14607        }
14608    });
14609}
14610
14611#[gpui::test]
14612async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14613    init_test(cx, |language_settings| {
14614        language_settings.defaults.completions = Some(CompletionSettingsContent {
14615            words: Some(WordsCompletionMode::Enabled),
14616            words_min_length: Some(0),
14617            lsp_insert_mode: Some(LspInsertMode::Insert),
14618            ..Default::default()
14619        });
14620    });
14621
14622    let mut cx = EditorLspTestContext::new_rust(
14623        lsp::ServerCapabilities {
14624            completion_provider: Some(lsp::CompletionOptions {
14625                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14626                ..lsp::CompletionOptions::default()
14627            }),
14628            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14629            ..lsp::ServerCapabilities::default()
14630        },
14631        cx,
14632    )
14633    .await;
14634
14635    let _completion_requests_handler =
14636        cx.lsp
14637            .server
14638            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14639                Ok(Some(lsp::CompletionResponse::Array(vec![
14640                    lsp::CompletionItem {
14641                        label: "first".into(),
14642                        ..lsp::CompletionItem::default()
14643                    },
14644                    lsp::CompletionItem {
14645                        label: "last".into(),
14646                        ..lsp::CompletionItem::default()
14647                    },
14648                ])))
14649            });
14650
14651    cx.set_state(indoc! {"ˇ
14652        first
14653        last
14654        second
14655    "});
14656    cx.simulate_keystroke(".");
14657    cx.executor().run_until_parked();
14658    cx.condition(|editor, _| editor.context_menu_visible())
14659        .await;
14660    cx.update_editor(|editor, _, _| {
14661        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14662        {
14663            assert_eq!(
14664                completion_menu_entries(menu),
14665                &["first", "last", "second"],
14666                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14667            );
14668        } else {
14669            panic!("expected completion menu to be open");
14670        }
14671    });
14672}
14673
14674#[gpui::test]
14675async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14676    init_test(cx, |language_settings| {
14677        language_settings.defaults.completions = Some(CompletionSettingsContent {
14678            words: Some(WordsCompletionMode::Disabled),
14679            words_min_length: Some(0),
14680            lsp_insert_mode: Some(LspInsertMode::Insert),
14681            ..Default::default()
14682        });
14683    });
14684
14685    let mut cx = EditorLspTestContext::new_rust(
14686        lsp::ServerCapabilities {
14687            completion_provider: Some(lsp::CompletionOptions {
14688                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14689                ..lsp::CompletionOptions::default()
14690            }),
14691            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14692            ..lsp::ServerCapabilities::default()
14693        },
14694        cx,
14695    )
14696    .await;
14697
14698    let _completion_requests_handler =
14699        cx.lsp
14700            .server
14701            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14702                panic!("LSP completions should not be queried when dealing with word completions")
14703            });
14704
14705    cx.set_state(indoc! {"ˇ
14706        first
14707        last
14708        second
14709    "});
14710    cx.update_editor(|editor, window, cx| {
14711        editor.show_word_completions(&ShowWordCompletions, window, cx);
14712    });
14713    cx.executor().run_until_parked();
14714    cx.condition(|editor, _| editor.context_menu_visible())
14715        .await;
14716    cx.update_editor(|editor, _, _| {
14717        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14718        {
14719            assert_eq!(
14720                completion_menu_entries(menu),
14721                &["first", "last", "second"],
14722                "`ShowWordCompletions` action should show word completions"
14723            );
14724        } else {
14725            panic!("expected completion menu to be open");
14726        }
14727    });
14728
14729    cx.simulate_keystroke("l");
14730    cx.executor().run_until_parked();
14731    cx.condition(|editor, _| editor.context_menu_visible())
14732        .await;
14733    cx.update_editor(|editor, _, _| {
14734        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14735        {
14736            assert_eq!(
14737                completion_menu_entries(menu),
14738                &["last"],
14739                "After showing word completions, further editing should filter them and not query the LSP"
14740            );
14741        } else {
14742            panic!("expected completion menu to be open");
14743        }
14744    });
14745}
14746
14747#[gpui::test]
14748async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14749    init_test(cx, |language_settings| {
14750        language_settings.defaults.completions = Some(CompletionSettingsContent {
14751            words_min_length: Some(0),
14752            lsp: Some(false),
14753            lsp_insert_mode: Some(LspInsertMode::Insert),
14754            ..Default::default()
14755        });
14756    });
14757
14758    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14759
14760    cx.set_state(indoc! {"ˇ
14761        0_usize
14762        let
14763        33
14764        4.5f32
14765    "});
14766    cx.update_editor(|editor, window, cx| {
14767        editor.show_completions(&ShowCompletions::default(), window, cx);
14768    });
14769    cx.executor().run_until_parked();
14770    cx.condition(|editor, _| editor.context_menu_visible())
14771        .await;
14772    cx.update_editor(|editor, window, cx| {
14773        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14774        {
14775            assert_eq!(
14776                completion_menu_entries(menu),
14777                &["let"],
14778                "With no digits in the completion query, no digits should be in the word completions"
14779            );
14780        } else {
14781            panic!("expected completion menu to be open");
14782        }
14783        editor.cancel(&Cancel, window, cx);
14784    });
14785
14786    cx.set_state(indoc! {"14787        0_usize
14788        let
14789        3
14790        33.35f32
14791    "});
14792    cx.update_editor(|editor, window, cx| {
14793        editor.show_completions(&ShowCompletions::default(), window, cx);
14794    });
14795    cx.executor().run_until_parked();
14796    cx.condition(|editor, _| editor.context_menu_visible())
14797        .await;
14798    cx.update_editor(|editor, _, _| {
14799        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14800        {
14801            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14802                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14803        } else {
14804            panic!("expected completion menu to be open");
14805        }
14806    });
14807}
14808
14809#[gpui::test]
14810async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14811    init_test(cx, |language_settings| {
14812        language_settings.defaults.completions = Some(CompletionSettingsContent {
14813            words: Some(WordsCompletionMode::Enabled),
14814            words_min_length: Some(3),
14815            lsp_insert_mode: Some(LspInsertMode::Insert),
14816            ..Default::default()
14817        });
14818    });
14819
14820    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14821    cx.set_state(indoc! {"ˇ
14822        wow
14823        wowen
14824        wowser
14825    "});
14826    cx.simulate_keystroke("w");
14827    cx.executor().run_until_parked();
14828    cx.update_editor(|editor, _, _| {
14829        if editor.context_menu.borrow_mut().is_some() {
14830            panic!(
14831                "expected completion menu to be hidden, as words completion threshold is not met"
14832            );
14833        }
14834    });
14835
14836    cx.update_editor(|editor, window, cx| {
14837        editor.show_word_completions(&ShowWordCompletions, window, cx);
14838    });
14839    cx.executor().run_until_parked();
14840    cx.update_editor(|editor, window, cx| {
14841        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14842        {
14843            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");
14844        } else {
14845            panic!("expected completion menu to be open after the word completions are called with an action");
14846        }
14847
14848        editor.cancel(&Cancel, window, cx);
14849    });
14850    cx.update_editor(|editor, _, _| {
14851        if editor.context_menu.borrow_mut().is_some() {
14852            panic!("expected completion menu to be hidden after canceling");
14853        }
14854    });
14855
14856    cx.simulate_keystroke("o");
14857    cx.executor().run_until_parked();
14858    cx.update_editor(|editor, _, _| {
14859        if editor.context_menu.borrow_mut().is_some() {
14860            panic!(
14861                "expected completion menu to be hidden, as words completion threshold is not met still"
14862            );
14863        }
14864    });
14865
14866    cx.simulate_keystroke("w");
14867    cx.executor().run_until_parked();
14868    cx.update_editor(|editor, _, _| {
14869        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14870        {
14871            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14872        } else {
14873            panic!("expected completion menu to be open after the word completions threshold is met");
14874        }
14875    });
14876}
14877
14878#[gpui::test]
14879async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14880    init_test(cx, |language_settings| {
14881        language_settings.defaults.completions = Some(CompletionSettingsContent {
14882            words: Some(WordsCompletionMode::Enabled),
14883            words_min_length: Some(0),
14884            lsp_insert_mode: Some(LspInsertMode::Insert),
14885            ..Default::default()
14886        });
14887    });
14888
14889    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14890    cx.update_editor(|editor, _, _| {
14891        editor.disable_word_completions();
14892    });
14893    cx.set_state(indoc! {"ˇ
14894        wow
14895        wowen
14896        wowser
14897    "});
14898    cx.simulate_keystroke("w");
14899    cx.executor().run_until_parked();
14900    cx.update_editor(|editor, _, _| {
14901        if editor.context_menu.borrow_mut().is_some() {
14902            panic!(
14903                "expected completion menu to be hidden, as words completion are disabled for this editor"
14904            );
14905        }
14906    });
14907
14908    cx.update_editor(|editor, window, cx| {
14909        editor.show_word_completions(&ShowWordCompletions, window, cx);
14910    });
14911    cx.executor().run_until_parked();
14912    cx.update_editor(|editor, _, _| {
14913        if editor.context_menu.borrow_mut().is_some() {
14914            panic!(
14915                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14916            );
14917        }
14918    });
14919}
14920
14921fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14922    let position = || lsp::Position {
14923        line: params.text_document_position.position.line,
14924        character: params.text_document_position.position.character,
14925    };
14926    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14927        range: lsp::Range {
14928            start: position(),
14929            end: position(),
14930        },
14931        new_text: text.to_string(),
14932    }))
14933}
14934
14935#[gpui::test]
14936async fn test_multiline_completion(cx: &mut TestAppContext) {
14937    init_test(cx, |_| {});
14938
14939    let fs = FakeFs::new(cx.executor());
14940    fs.insert_tree(
14941        path!("/a"),
14942        json!({
14943            "main.ts": "a",
14944        }),
14945    )
14946    .await;
14947
14948    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14949    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14950    let typescript_language = Arc::new(Language::new(
14951        LanguageConfig {
14952            name: "TypeScript".into(),
14953            matcher: LanguageMatcher {
14954                path_suffixes: vec!["ts".to_string()],
14955                ..LanguageMatcher::default()
14956            },
14957            line_comments: vec!["// ".into()],
14958            ..LanguageConfig::default()
14959        },
14960        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14961    ));
14962    language_registry.add(typescript_language.clone());
14963    let mut fake_servers = language_registry.register_fake_lsp(
14964        "TypeScript",
14965        FakeLspAdapter {
14966            capabilities: lsp::ServerCapabilities {
14967                completion_provider: Some(lsp::CompletionOptions {
14968                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14969                    ..lsp::CompletionOptions::default()
14970                }),
14971                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14972                ..lsp::ServerCapabilities::default()
14973            },
14974            // Emulate vtsls label generation
14975            label_for_completion: Some(Box::new(|item, _| {
14976                let text = if let Some(description) = item
14977                    .label_details
14978                    .as_ref()
14979                    .and_then(|label_details| label_details.description.as_ref())
14980                {
14981                    format!("{} {}", item.label, description)
14982                } else if let Some(detail) = &item.detail {
14983                    format!("{} {}", item.label, detail)
14984                } else {
14985                    item.label.clone()
14986                };
14987                Some(language::CodeLabel::plain(text, None))
14988            })),
14989            ..FakeLspAdapter::default()
14990        },
14991    );
14992    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14993    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14994    let worktree_id = workspace
14995        .update(cx, |workspace, _window, cx| {
14996            workspace.project().update(cx, |project, cx| {
14997                project.worktrees(cx).next().unwrap().read(cx).id()
14998            })
14999        })
15000        .unwrap();
15001    let _buffer = project
15002        .update(cx, |project, cx| {
15003            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15004        })
15005        .await
15006        .unwrap();
15007    let editor = workspace
15008        .update(cx, |workspace, window, cx| {
15009            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15010        })
15011        .unwrap()
15012        .await
15013        .unwrap()
15014        .downcast::<Editor>()
15015        .unwrap();
15016    let fake_server = fake_servers.next().await.unwrap();
15017
15018    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15019    let multiline_label_2 = "a\nb\nc\n";
15020    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15021    let multiline_description = "d\ne\nf\n";
15022    let multiline_detail_2 = "g\nh\ni\n";
15023
15024    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15025        move |params, _| async move {
15026            Ok(Some(lsp::CompletionResponse::Array(vec![
15027                lsp::CompletionItem {
15028                    label: multiline_label.to_string(),
15029                    text_edit: gen_text_edit(&params, "new_text_1"),
15030                    ..lsp::CompletionItem::default()
15031                },
15032                lsp::CompletionItem {
15033                    label: "single line label 1".to_string(),
15034                    detail: Some(multiline_detail.to_string()),
15035                    text_edit: gen_text_edit(&params, "new_text_2"),
15036                    ..lsp::CompletionItem::default()
15037                },
15038                lsp::CompletionItem {
15039                    label: "single line label 2".to_string(),
15040                    label_details: Some(lsp::CompletionItemLabelDetails {
15041                        description: Some(multiline_description.to_string()),
15042                        detail: None,
15043                    }),
15044                    text_edit: gen_text_edit(&params, "new_text_2"),
15045                    ..lsp::CompletionItem::default()
15046                },
15047                lsp::CompletionItem {
15048                    label: multiline_label_2.to_string(),
15049                    detail: Some(multiline_detail_2.to_string()),
15050                    text_edit: gen_text_edit(&params, "new_text_3"),
15051                    ..lsp::CompletionItem::default()
15052                },
15053                lsp::CompletionItem {
15054                    label: "Label with many     spaces and \t but without newlines".to_string(),
15055                    detail: Some(
15056                        "Details with many     spaces and \t but without newlines".to_string(),
15057                    ),
15058                    text_edit: gen_text_edit(&params, "new_text_4"),
15059                    ..lsp::CompletionItem::default()
15060                },
15061            ])))
15062        },
15063    );
15064
15065    editor.update_in(cx, |editor, window, cx| {
15066        cx.focus_self(window);
15067        editor.move_to_end(&MoveToEnd, window, cx);
15068        editor.handle_input(".", window, cx);
15069    });
15070    cx.run_until_parked();
15071    completion_handle.next().await.unwrap();
15072
15073    editor.update(cx, |editor, _| {
15074        assert!(editor.context_menu_visible());
15075        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15076        {
15077            let completion_labels = menu
15078                .completions
15079                .borrow()
15080                .iter()
15081                .map(|c| c.label.text.clone())
15082                .collect::<Vec<_>>();
15083            assert_eq!(
15084                completion_labels,
15085                &[
15086                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15087                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15088                    "single line label 2 d e f ",
15089                    "a b c g h i ",
15090                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15091                ],
15092                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15093            );
15094
15095            for completion in menu
15096                .completions
15097                .borrow()
15098                .iter() {
15099                    assert_eq!(
15100                        completion.label.filter_range,
15101                        0..completion.label.text.len(),
15102                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15103                    );
15104                }
15105        } else {
15106            panic!("expected completion menu to be open");
15107        }
15108    });
15109}
15110
15111#[gpui::test]
15112async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15113    init_test(cx, |_| {});
15114    let mut cx = EditorLspTestContext::new_rust(
15115        lsp::ServerCapabilities {
15116            completion_provider: Some(lsp::CompletionOptions {
15117                trigger_characters: Some(vec![".".to_string()]),
15118                ..Default::default()
15119            }),
15120            ..Default::default()
15121        },
15122        cx,
15123    )
15124    .await;
15125    cx.lsp
15126        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15127            Ok(Some(lsp::CompletionResponse::Array(vec![
15128                lsp::CompletionItem {
15129                    label: "first".into(),
15130                    ..Default::default()
15131                },
15132                lsp::CompletionItem {
15133                    label: "last".into(),
15134                    ..Default::default()
15135                },
15136            ])))
15137        });
15138    cx.set_state("variableˇ");
15139    cx.simulate_keystroke(".");
15140    cx.executor().run_until_parked();
15141
15142    cx.update_editor(|editor, _, _| {
15143        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15144        {
15145            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15146        } else {
15147            panic!("expected completion menu to be open");
15148        }
15149    });
15150
15151    cx.update_editor(|editor, window, cx| {
15152        editor.move_page_down(&MovePageDown::default(), window, cx);
15153        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15154        {
15155            assert!(
15156                menu.selected_item == 1,
15157                "expected PageDown to select the last item from the context menu"
15158            );
15159        } else {
15160            panic!("expected completion menu to stay open after PageDown");
15161        }
15162    });
15163
15164    cx.update_editor(|editor, window, cx| {
15165        editor.move_page_up(&MovePageUp::default(), window, cx);
15166        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15167        {
15168            assert!(
15169                menu.selected_item == 0,
15170                "expected PageUp to select the first item from the context menu"
15171            );
15172        } else {
15173            panic!("expected completion menu to stay open after PageUp");
15174        }
15175    });
15176}
15177
15178#[gpui::test]
15179async fn test_as_is_completions(cx: &mut TestAppContext) {
15180    init_test(cx, |_| {});
15181    let mut cx = EditorLspTestContext::new_rust(
15182        lsp::ServerCapabilities {
15183            completion_provider: Some(lsp::CompletionOptions {
15184                ..Default::default()
15185            }),
15186            ..Default::default()
15187        },
15188        cx,
15189    )
15190    .await;
15191    cx.lsp
15192        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15193            Ok(Some(lsp::CompletionResponse::Array(vec![
15194                lsp::CompletionItem {
15195                    label: "unsafe".into(),
15196                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15197                        range: lsp::Range {
15198                            start: lsp::Position {
15199                                line: 1,
15200                                character: 2,
15201                            },
15202                            end: lsp::Position {
15203                                line: 1,
15204                                character: 3,
15205                            },
15206                        },
15207                        new_text: "unsafe".to_string(),
15208                    })),
15209                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15210                    ..Default::default()
15211                },
15212            ])))
15213        });
15214    cx.set_state("fn a() {}\n");
15215    cx.executor().run_until_parked();
15216    cx.update_editor(|editor, window, cx| {
15217        editor.show_completions(
15218            &ShowCompletions {
15219                trigger: Some("\n".into()),
15220            },
15221            window,
15222            cx,
15223        );
15224    });
15225    cx.executor().run_until_parked();
15226
15227    cx.update_editor(|editor, window, cx| {
15228        editor.confirm_completion(&Default::default(), window, cx)
15229    });
15230    cx.executor().run_until_parked();
15231    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15232}
15233
15234#[gpui::test]
15235async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15236    init_test(cx, |_| {});
15237    let language =
15238        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15239    let mut cx = EditorLspTestContext::new(
15240        language,
15241        lsp::ServerCapabilities {
15242            completion_provider: Some(lsp::CompletionOptions {
15243                ..lsp::CompletionOptions::default()
15244            }),
15245            ..lsp::ServerCapabilities::default()
15246        },
15247        cx,
15248    )
15249    .await;
15250
15251    cx.set_state(
15252        "#ifndef BAR_H
15253#define BAR_H
15254
15255#include <stdbool.h>
15256
15257int fn_branch(bool do_branch1, bool do_branch2);
15258
15259#endif // BAR_H
15260ˇ",
15261    );
15262    cx.executor().run_until_parked();
15263    cx.update_editor(|editor, window, cx| {
15264        editor.handle_input("#", window, cx);
15265    });
15266    cx.executor().run_until_parked();
15267    cx.update_editor(|editor, window, cx| {
15268        editor.handle_input("i", window, cx);
15269    });
15270    cx.executor().run_until_parked();
15271    cx.update_editor(|editor, window, cx| {
15272        editor.handle_input("n", window, cx);
15273    });
15274    cx.executor().run_until_parked();
15275    cx.assert_editor_state(
15276        "#ifndef BAR_H
15277#define BAR_H
15278
15279#include <stdbool.h>
15280
15281int fn_branch(bool do_branch1, bool do_branch2);
15282
15283#endif // BAR_H
15284#inˇ",
15285    );
15286
15287    cx.lsp
15288        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15289            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15290                is_incomplete: false,
15291                item_defaults: None,
15292                items: vec![lsp::CompletionItem {
15293                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15294                    label_details: Some(lsp::CompletionItemLabelDetails {
15295                        detail: Some("header".to_string()),
15296                        description: None,
15297                    }),
15298                    label: " include".to_string(),
15299                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15300                        range: lsp::Range {
15301                            start: lsp::Position {
15302                                line: 8,
15303                                character: 1,
15304                            },
15305                            end: lsp::Position {
15306                                line: 8,
15307                                character: 1,
15308                            },
15309                        },
15310                        new_text: "include \"$0\"".to_string(),
15311                    })),
15312                    sort_text: Some("40b67681include".to_string()),
15313                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15314                    filter_text: Some("include".to_string()),
15315                    insert_text: Some("include \"$0\"".to_string()),
15316                    ..lsp::CompletionItem::default()
15317                }],
15318            })))
15319        });
15320    cx.update_editor(|editor, window, cx| {
15321        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15322    });
15323    cx.executor().run_until_parked();
15324    cx.update_editor(|editor, window, cx| {
15325        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15326    });
15327    cx.executor().run_until_parked();
15328    cx.assert_editor_state(
15329        "#ifndef BAR_H
15330#define BAR_H
15331
15332#include <stdbool.h>
15333
15334int fn_branch(bool do_branch1, bool do_branch2);
15335
15336#endif // BAR_H
15337#include \"ˇ\"",
15338    );
15339
15340    cx.lsp
15341        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15342            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15343                is_incomplete: true,
15344                item_defaults: None,
15345                items: vec![lsp::CompletionItem {
15346                    kind: Some(lsp::CompletionItemKind::FILE),
15347                    label: "AGL/".to_string(),
15348                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15349                        range: lsp::Range {
15350                            start: lsp::Position {
15351                                line: 8,
15352                                character: 10,
15353                            },
15354                            end: lsp::Position {
15355                                line: 8,
15356                                character: 11,
15357                            },
15358                        },
15359                        new_text: "AGL/".to_string(),
15360                    })),
15361                    sort_text: Some("40b67681AGL/".to_string()),
15362                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15363                    filter_text: Some("AGL/".to_string()),
15364                    insert_text: Some("AGL/".to_string()),
15365                    ..lsp::CompletionItem::default()
15366                }],
15367            })))
15368        });
15369    cx.update_editor(|editor, window, cx| {
15370        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15371    });
15372    cx.executor().run_until_parked();
15373    cx.update_editor(|editor, window, cx| {
15374        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15375    });
15376    cx.executor().run_until_parked();
15377    cx.assert_editor_state(
15378        r##"#ifndef BAR_H
15379#define BAR_H
15380
15381#include <stdbool.h>
15382
15383int fn_branch(bool do_branch1, bool do_branch2);
15384
15385#endif // BAR_H
15386#include "AGL/ˇ"##,
15387    );
15388
15389    cx.update_editor(|editor, window, cx| {
15390        editor.handle_input("\"", window, cx);
15391    });
15392    cx.executor().run_until_parked();
15393    cx.assert_editor_state(
15394        r##"#ifndef BAR_H
15395#define BAR_H
15396
15397#include <stdbool.h>
15398
15399int fn_branch(bool do_branch1, bool do_branch2);
15400
15401#endif // BAR_H
15402#include "AGL/"ˇ"##,
15403    );
15404}
15405
15406#[gpui::test]
15407async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15408    init_test(cx, |_| {});
15409
15410    let mut cx = EditorLspTestContext::new_rust(
15411        lsp::ServerCapabilities {
15412            completion_provider: Some(lsp::CompletionOptions {
15413                trigger_characters: Some(vec![".".to_string()]),
15414                resolve_provider: Some(true),
15415                ..Default::default()
15416            }),
15417            ..Default::default()
15418        },
15419        cx,
15420    )
15421    .await;
15422
15423    cx.set_state("fn main() { let a = 2ˇ; }");
15424    cx.simulate_keystroke(".");
15425    let completion_item = lsp::CompletionItem {
15426        label: "Some".into(),
15427        kind: Some(lsp::CompletionItemKind::SNIPPET),
15428        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15429        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15430            kind: lsp::MarkupKind::Markdown,
15431            value: "```rust\nSome(2)\n```".to_string(),
15432        })),
15433        deprecated: Some(false),
15434        sort_text: Some("Some".to_string()),
15435        filter_text: Some("Some".to_string()),
15436        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15437        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15438            range: lsp::Range {
15439                start: lsp::Position {
15440                    line: 0,
15441                    character: 22,
15442                },
15443                end: lsp::Position {
15444                    line: 0,
15445                    character: 22,
15446                },
15447            },
15448            new_text: "Some(2)".to_string(),
15449        })),
15450        additional_text_edits: Some(vec![lsp::TextEdit {
15451            range: lsp::Range {
15452                start: lsp::Position {
15453                    line: 0,
15454                    character: 20,
15455                },
15456                end: lsp::Position {
15457                    line: 0,
15458                    character: 22,
15459                },
15460            },
15461            new_text: "".to_string(),
15462        }]),
15463        ..Default::default()
15464    };
15465
15466    let closure_completion_item = completion_item.clone();
15467    let counter = Arc::new(AtomicUsize::new(0));
15468    let counter_clone = counter.clone();
15469    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15470        let task_completion_item = closure_completion_item.clone();
15471        counter_clone.fetch_add(1, atomic::Ordering::Release);
15472        async move {
15473            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15474                is_incomplete: true,
15475                item_defaults: None,
15476                items: vec![task_completion_item],
15477            })))
15478        }
15479    });
15480
15481    cx.condition(|editor, _| editor.context_menu_visible())
15482        .await;
15483    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15484    assert!(request.next().await.is_some());
15485    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15486
15487    cx.simulate_keystrokes("S o m");
15488    cx.condition(|editor, _| editor.context_menu_visible())
15489        .await;
15490    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15491    assert!(request.next().await.is_some());
15492    assert!(request.next().await.is_some());
15493    assert!(request.next().await.is_some());
15494    request.close();
15495    assert!(request.next().await.is_none());
15496    assert_eq!(
15497        counter.load(atomic::Ordering::Acquire),
15498        4,
15499        "With the completions menu open, only one LSP request should happen per input"
15500    );
15501}
15502
15503#[gpui::test]
15504async fn test_toggle_comment(cx: &mut TestAppContext) {
15505    init_test(cx, |_| {});
15506    let mut cx = EditorTestContext::new(cx).await;
15507    let language = Arc::new(Language::new(
15508        LanguageConfig {
15509            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15510            ..Default::default()
15511        },
15512        Some(tree_sitter_rust::LANGUAGE.into()),
15513    ));
15514    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15515
15516    // If multiple selections intersect a line, the line is only toggled once.
15517    cx.set_state(indoc! {"
15518        fn a() {
15519            «//b();
15520            ˇ»// «c();
15521            //ˇ»  d();
15522        }
15523    "});
15524
15525    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15526
15527    cx.assert_editor_state(indoc! {"
15528        fn a() {
15529            «b();
15530            c();
15531            ˇ» d();
15532        }
15533    "});
15534
15535    // The comment prefix is inserted at the same column for every line in a
15536    // selection.
15537    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15538
15539    cx.assert_editor_state(indoc! {"
15540        fn a() {
15541            // «b();
15542            // c();
15543            ˇ»//  d();
15544        }
15545    "});
15546
15547    // If a selection ends at the beginning of a line, that line is not toggled.
15548    cx.set_selections_state(indoc! {"
15549        fn a() {
15550            // b();
15551            «// c();
15552        ˇ»    //  d();
15553        }
15554    "});
15555
15556    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15557
15558    cx.assert_editor_state(indoc! {"
15559        fn a() {
15560            // b();
15561            «c();
15562        ˇ»    //  d();
15563        }
15564    "});
15565
15566    // If a selection span a single line and is empty, the line is toggled.
15567    cx.set_state(indoc! {"
15568        fn a() {
15569            a();
15570            b();
15571        ˇ
15572        }
15573    "});
15574
15575    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15576
15577    cx.assert_editor_state(indoc! {"
15578        fn a() {
15579            a();
15580            b();
15581        //•ˇ
15582        }
15583    "});
15584
15585    // If a selection span multiple lines, empty lines are not toggled.
15586    cx.set_state(indoc! {"
15587        fn a() {
15588            «a();
15589
15590            c();ˇ»
15591        }
15592    "});
15593
15594    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15595
15596    cx.assert_editor_state(indoc! {"
15597        fn a() {
15598            // «a();
15599
15600            // c();ˇ»
15601        }
15602    "});
15603
15604    // If a selection includes multiple comment prefixes, all lines are uncommented.
15605    cx.set_state(indoc! {"
15606        fn a() {
15607            «// a();
15608            /// b();
15609            //! c();ˇ»
15610        }
15611    "});
15612
15613    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15614
15615    cx.assert_editor_state(indoc! {"
15616        fn a() {
15617            «a();
15618            b();
15619            c();ˇ»
15620        }
15621    "});
15622}
15623
15624#[gpui::test]
15625async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15626    init_test(cx, |_| {});
15627    let mut cx = EditorTestContext::new(cx).await;
15628    let language = Arc::new(Language::new(
15629        LanguageConfig {
15630            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15631            ..Default::default()
15632        },
15633        Some(tree_sitter_rust::LANGUAGE.into()),
15634    ));
15635    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15636
15637    let toggle_comments = &ToggleComments {
15638        advance_downwards: false,
15639        ignore_indent: true,
15640    };
15641
15642    // If multiple selections intersect a line, the line is only toggled once.
15643    cx.set_state(indoc! {"
15644        fn a() {
15645        //    «b();
15646        //    c();
15647        //    ˇ» d();
15648        }
15649    "});
15650
15651    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15652
15653    cx.assert_editor_state(indoc! {"
15654        fn a() {
15655            «b();
15656            c();
15657            ˇ» d();
15658        }
15659    "});
15660
15661    // The comment prefix is inserted at the beginning of each line
15662    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15663
15664    cx.assert_editor_state(indoc! {"
15665        fn a() {
15666        //    «b();
15667        //    c();
15668        //    ˇ» d();
15669        }
15670    "});
15671
15672    // If a selection ends at the beginning of a line, that line is not toggled.
15673    cx.set_selections_state(indoc! {"
15674        fn a() {
15675        //    b();
15676        //    «c();
15677        ˇ»//     d();
15678        }
15679    "});
15680
15681    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15682
15683    cx.assert_editor_state(indoc! {"
15684        fn a() {
15685        //    b();
15686            «c();
15687        ˇ»//     d();
15688        }
15689    "});
15690
15691    // If a selection span a single line and is empty, the line is toggled.
15692    cx.set_state(indoc! {"
15693        fn a() {
15694            a();
15695            b();
15696        ˇ
15697        }
15698    "});
15699
15700    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15701
15702    cx.assert_editor_state(indoc! {"
15703        fn a() {
15704            a();
15705            b();
15706        //ˇ
15707        }
15708    "});
15709
15710    // If a selection span multiple lines, empty lines are not toggled.
15711    cx.set_state(indoc! {"
15712        fn a() {
15713            «a();
15714
15715            c();ˇ»
15716        }
15717    "});
15718
15719    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15720
15721    cx.assert_editor_state(indoc! {"
15722        fn a() {
15723        //    «a();
15724
15725        //    c();ˇ»
15726        }
15727    "});
15728
15729    // If a selection includes multiple comment prefixes, all lines are uncommented.
15730    cx.set_state(indoc! {"
15731        fn a() {
15732        //    «a();
15733        ///    b();
15734        //!    c();ˇ»
15735        }
15736    "});
15737
15738    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15739
15740    cx.assert_editor_state(indoc! {"
15741        fn a() {
15742            «a();
15743            b();
15744            c();ˇ»
15745        }
15746    "});
15747}
15748
15749#[gpui::test]
15750async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15751    init_test(cx, |_| {});
15752
15753    let language = Arc::new(Language::new(
15754        LanguageConfig {
15755            line_comments: vec!["// ".into()],
15756            ..Default::default()
15757        },
15758        Some(tree_sitter_rust::LANGUAGE.into()),
15759    ));
15760
15761    let mut cx = EditorTestContext::new(cx).await;
15762
15763    cx.language_registry().add(language.clone());
15764    cx.update_buffer(|buffer, cx| {
15765        buffer.set_language(Some(language), cx);
15766    });
15767
15768    let toggle_comments = &ToggleComments {
15769        advance_downwards: true,
15770        ignore_indent: false,
15771    };
15772
15773    // Single cursor on one line -> advance
15774    // Cursor moves horizontally 3 characters as well on non-blank line
15775    cx.set_state(indoc!(
15776        "fn a() {
15777             ˇdog();
15778             cat();
15779        }"
15780    ));
15781    cx.update_editor(|editor, window, cx| {
15782        editor.toggle_comments(toggle_comments, window, cx);
15783    });
15784    cx.assert_editor_state(indoc!(
15785        "fn a() {
15786             // dog();
15787             catˇ();
15788        }"
15789    ));
15790
15791    // Single selection on one line -> don't advance
15792    cx.set_state(indoc!(
15793        "fn a() {
15794             «dog()ˇ»;
15795             cat();
15796        }"
15797    ));
15798    cx.update_editor(|editor, window, cx| {
15799        editor.toggle_comments(toggle_comments, window, cx);
15800    });
15801    cx.assert_editor_state(indoc!(
15802        "fn a() {
15803             // «dog()ˇ»;
15804             cat();
15805        }"
15806    ));
15807
15808    // Multiple cursors on one line -> advance
15809    cx.set_state(indoc!(
15810        "fn a() {
15811             ˇdˇog();
15812             cat();
15813        }"
15814    ));
15815    cx.update_editor(|editor, window, cx| {
15816        editor.toggle_comments(toggle_comments, window, cx);
15817    });
15818    cx.assert_editor_state(indoc!(
15819        "fn a() {
15820             // dog();
15821             catˇ(ˇ);
15822        }"
15823    ));
15824
15825    // Multiple cursors on one line, with selection -> don't advance
15826    cx.set_state(indoc!(
15827        "fn a() {
15828             ˇdˇog«()ˇ»;
15829             cat();
15830        }"
15831    ));
15832    cx.update_editor(|editor, window, cx| {
15833        editor.toggle_comments(toggle_comments, window, cx);
15834    });
15835    cx.assert_editor_state(indoc!(
15836        "fn a() {
15837             // ˇdˇog«()ˇ»;
15838             cat();
15839        }"
15840    ));
15841
15842    // Single cursor on one line -> advance
15843    // Cursor moves to column 0 on blank line
15844    cx.set_state(indoc!(
15845        "fn a() {
15846             ˇdog();
15847
15848             cat();
15849        }"
15850    ));
15851    cx.update_editor(|editor, window, cx| {
15852        editor.toggle_comments(toggle_comments, window, cx);
15853    });
15854    cx.assert_editor_state(indoc!(
15855        "fn a() {
15856             // dog();
15857        ˇ
15858             cat();
15859        }"
15860    ));
15861
15862    // Single cursor on one line -> advance
15863    // Cursor starts and ends at column 0
15864    cx.set_state(indoc!(
15865        "fn a() {
15866         ˇ    dog();
15867             cat();
15868        }"
15869    ));
15870    cx.update_editor(|editor, window, cx| {
15871        editor.toggle_comments(toggle_comments, window, cx);
15872    });
15873    cx.assert_editor_state(indoc!(
15874        "fn a() {
15875             // dog();
15876         ˇ    cat();
15877        }"
15878    ));
15879}
15880
15881#[gpui::test]
15882async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15883    init_test(cx, |_| {});
15884
15885    let mut cx = EditorTestContext::new(cx).await;
15886
15887    let html_language = Arc::new(
15888        Language::new(
15889            LanguageConfig {
15890                name: "HTML".into(),
15891                block_comment: Some(BlockCommentConfig {
15892                    start: "<!-- ".into(),
15893                    prefix: "".into(),
15894                    end: " -->".into(),
15895                    tab_size: 0,
15896                }),
15897                ..Default::default()
15898            },
15899            Some(tree_sitter_html::LANGUAGE.into()),
15900        )
15901        .with_injection_query(
15902            r#"
15903            (script_element
15904                (raw_text) @injection.content
15905                (#set! injection.language "javascript"))
15906            "#,
15907        )
15908        .unwrap(),
15909    );
15910
15911    let javascript_language = Arc::new(Language::new(
15912        LanguageConfig {
15913            name: "JavaScript".into(),
15914            line_comments: vec!["// ".into()],
15915            ..Default::default()
15916        },
15917        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15918    ));
15919
15920    cx.language_registry().add(html_language.clone());
15921    cx.language_registry().add(javascript_language);
15922    cx.update_buffer(|buffer, cx| {
15923        buffer.set_language(Some(html_language), cx);
15924    });
15925
15926    // Toggle comments for empty selections
15927    cx.set_state(
15928        &r#"
15929            <p>A</p>ˇ
15930            <p>B</p>ˇ
15931            <p>C</p>ˇ
15932        "#
15933        .unindent(),
15934    );
15935    cx.update_editor(|editor, window, cx| {
15936        editor.toggle_comments(&ToggleComments::default(), window, cx)
15937    });
15938    cx.assert_editor_state(
15939        &r#"
15940            <!-- <p>A</p>ˇ -->
15941            <!-- <p>B</p>ˇ -->
15942            <!-- <p>C</p>ˇ -->
15943        "#
15944        .unindent(),
15945    );
15946    cx.update_editor(|editor, window, cx| {
15947        editor.toggle_comments(&ToggleComments::default(), window, cx)
15948    });
15949    cx.assert_editor_state(
15950        &r#"
15951            <p>A</p>ˇ
15952            <p>B</p>ˇ
15953            <p>C</p>ˇ
15954        "#
15955        .unindent(),
15956    );
15957
15958    // Toggle comments for mixture of empty and non-empty selections, where
15959    // multiple selections occupy a given line.
15960    cx.set_state(
15961        &r#"
15962            <p>A«</p>
15963            <p>ˇ»B</p>ˇ
15964            <p>C«</p>
15965            <p>ˇ»D</p>ˇ
15966        "#
15967        .unindent(),
15968    );
15969
15970    cx.update_editor(|editor, window, cx| {
15971        editor.toggle_comments(&ToggleComments::default(), window, cx)
15972    });
15973    cx.assert_editor_state(
15974        &r#"
15975            <!-- <p>A«</p>
15976            <p>ˇ»B</p>ˇ -->
15977            <!-- <p>C«</p>
15978            <p>ˇ»D</p>ˇ -->
15979        "#
15980        .unindent(),
15981    );
15982    cx.update_editor(|editor, window, cx| {
15983        editor.toggle_comments(&ToggleComments::default(), window, cx)
15984    });
15985    cx.assert_editor_state(
15986        &r#"
15987            <p>A«</p>
15988            <p>ˇ»B</p>ˇ
15989            <p>C«</p>
15990            <p>ˇ»D</p>ˇ
15991        "#
15992        .unindent(),
15993    );
15994
15995    // Toggle comments when different languages are active for different
15996    // selections.
15997    cx.set_state(
15998        &r#"
15999            ˇ<script>
16000                ˇvar x = new Y();
16001            ˇ</script>
16002        "#
16003        .unindent(),
16004    );
16005    cx.executor().run_until_parked();
16006    cx.update_editor(|editor, window, cx| {
16007        editor.toggle_comments(&ToggleComments::default(), window, cx)
16008    });
16009    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16010    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16011    cx.assert_editor_state(
16012        &r#"
16013            <!-- ˇ<script> -->
16014                // ˇvar x = new Y();
16015            <!-- ˇ</script> -->
16016        "#
16017        .unindent(),
16018    );
16019}
16020
16021#[gpui::test]
16022fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16023    init_test(cx, |_| {});
16024
16025    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16026    let multibuffer = cx.new(|cx| {
16027        let mut multibuffer = MultiBuffer::new(ReadWrite);
16028        multibuffer.push_excerpts(
16029            buffer.clone(),
16030            [
16031                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16032                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16033            ],
16034            cx,
16035        );
16036        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16037        multibuffer
16038    });
16039
16040    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16041    editor.update_in(cx, |editor, window, cx| {
16042        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16043        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16044            s.select_ranges([
16045                Point::new(0, 0)..Point::new(0, 0),
16046                Point::new(1, 0)..Point::new(1, 0),
16047            ])
16048        });
16049
16050        editor.handle_input("X", window, cx);
16051        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16052        assert_eq!(
16053            editor.selections.ranges(&editor.display_snapshot(cx)),
16054            [
16055                Point::new(0, 1)..Point::new(0, 1),
16056                Point::new(1, 1)..Point::new(1, 1),
16057            ]
16058        );
16059
16060        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16062            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16063        });
16064        editor.backspace(&Default::default(), window, cx);
16065        assert_eq!(editor.text(cx), "Xa\nbbb");
16066        assert_eq!(
16067            editor.selections.ranges(&editor.display_snapshot(cx)),
16068            [Point::new(1, 0)..Point::new(1, 0)]
16069        );
16070
16071        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16072            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16073        });
16074        editor.backspace(&Default::default(), window, cx);
16075        assert_eq!(editor.text(cx), "X\nbb");
16076        assert_eq!(
16077            editor.selections.ranges(&editor.display_snapshot(cx)),
16078            [Point::new(0, 1)..Point::new(0, 1)]
16079        );
16080    });
16081}
16082
16083#[gpui::test]
16084fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16085    init_test(cx, |_| {});
16086
16087    let markers = vec![('[', ']').into(), ('(', ')').into()];
16088    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16089        indoc! {"
16090            [aaaa
16091            (bbbb]
16092            cccc)",
16093        },
16094        markers.clone(),
16095    );
16096    let excerpt_ranges = markers.into_iter().map(|marker| {
16097        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16098        ExcerptRange::new(context)
16099    });
16100    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16101    let multibuffer = cx.new(|cx| {
16102        let mut multibuffer = MultiBuffer::new(ReadWrite);
16103        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16104        multibuffer
16105    });
16106
16107    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16108    editor.update_in(cx, |editor, window, cx| {
16109        let (expected_text, selection_ranges) = marked_text_ranges(
16110            indoc! {"
16111                aaaa
16112                bˇbbb
16113                bˇbbˇb
16114                cccc"
16115            },
16116            true,
16117        );
16118        assert_eq!(editor.text(cx), expected_text);
16119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16120            s.select_ranges(selection_ranges)
16121        });
16122
16123        editor.handle_input("X", window, cx);
16124
16125        let (expected_text, expected_selections) = marked_text_ranges(
16126            indoc! {"
16127                aaaa
16128                bXˇbbXb
16129                bXˇbbXˇb
16130                cccc"
16131            },
16132            false,
16133        );
16134        assert_eq!(editor.text(cx), expected_text);
16135        assert_eq!(
16136            editor.selections.ranges(&editor.display_snapshot(cx)),
16137            expected_selections
16138        );
16139
16140        editor.newline(&Newline, window, cx);
16141        let (expected_text, expected_selections) = marked_text_ranges(
16142            indoc! {"
16143                aaaa
16144                bX
16145                ˇbbX
16146                b
16147                bX
16148                ˇbbX
16149                ˇb
16150                cccc"
16151            },
16152            false,
16153        );
16154        assert_eq!(editor.text(cx), expected_text);
16155        assert_eq!(
16156            editor.selections.ranges(&editor.display_snapshot(cx)),
16157            expected_selections
16158        );
16159    });
16160}
16161
16162#[gpui::test]
16163fn test_refresh_selections(cx: &mut TestAppContext) {
16164    init_test(cx, |_| {});
16165
16166    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16167    let mut excerpt1_id = None;
16168    let multibuffer = cx.new(|cx| {
16169        let mut multibuffer = MultiBuffer::new(ReadWrite);
16170        excerpt1_id = multibuffer
16171            .push_excerpts(
16172                buffer.clone(),
16173                [
16174                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16175                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16176                ],
16177                cx,
16178            )
16179            .into_iter()
16180            .next();
16181        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16182        multibuffer
16183    });
16184
16185    let editor = cx.add_window(|window, cx| {
16186        let mut editor = build_editor(multibuffer.clone(), window, cx);
16187        let snapshot = editor.snapshot(window, cx);
16188        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16189            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16190        });
16191        editor.begin_selection(
16192            Point::new(2, 1).to_display_point(&snapshot),
16193            true,
16194            1,
16195            window,
16196            cx,
16197        );
16198        assert_eq!(
16199            editor.selections.ranges(&editor.display_snapshot(cx)),
16200            [
16201                Point::new(1, 3)..Point::new(1, 3),
16202                Point::new(2, 1)..Point::new(2, 1),
16203            ]
16204        );
16205        editor
16206    });
16207
16208    // Refreshing selections is a no-op when excerpts haven't changed.
16209    _ = editor.update(cx, |editor, window, cx| {
16210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16211        assert_eq!(
16212            editor.selections.ranges(&editor.display_snapshot(cx)),
16213            [
16214                Point::new(1, 3)..Point::new(1, 3),
16215                Point::new(2, 1)..Point::new(2, 1),
16216            ]
16217        );
16218    });
16219
16220    multibuffer.update(cx, |multibuffer, cx| {
16221        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16222    });
16223    _ = editor.update(cx, |editor, window, cx| {
16224        // Removing an excerpt causes the first selection to become degenerate.
16225        assert_eq!(
16226            editor.selections.ranges(&editor.display_snapshot(cx)),
16227            [
16228                Point::new(0, 0)..Point::new(0, 0),
16229                Point::new(0, 1)..Point::new(0, 1)
16230            ]
16231        );
16232
16233        // Refreshing selections will relocate the first selection to the original buffer
16234        // location.
16235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16236        assert_eq!(
16237            editor.selections.ranges(&editor.display_snapshot(cx)),
16238            [
16239                Point::new(0, 1)..Point::new(0, 1),
16240                Point::new(0, 3)..Point::new(0, 3)
16241            ]
16242        );
16243        assert!(editor.selections.pending_anchor().is_some());
16244    });
16245}
16246
16247#[gpui::test]
16248fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16249    init_test(cx, |_| {});
16250
16251    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16252    let mut excerpt1_id = None;
16253    let multibuffer = cx.new(|cx| {
16254        let mut multibuffer = MultiBuffer::new(ReadWrite);
16255        excerpt1_id = multibuffer
16256            .push_excerpts(
16257                buffer.clone(),
16258                [
16259                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16260                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16261                ],
16262                cx,
16263            )
16264            .into_iter()
16265            .next();
16266        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16267        multibuffer
16268    });
16269
16270    let editor = cx.add_window(|window, cx| {
16271        let mut editor = build_editor(multibuffer.clone(), window, cx);
16272        let snapshot = editor.snapshot(window, cx);
16273        editor.begin_selection(
16274            Point::new(1, 3).to_display_point(&snapshot),
16275            false,
16276            1,
16277            window,
16278            cx,
16279        );
16280        assert_eq!(
16281            editor.selections.ranges(&editor.display_snapshot(cx)),
16282            [Point::new(1, 3)..Point::new(1, 3)]
16283        );
16284        editor
16285    });
16286
16287    multibuffer.update(cx, |multibuffer, cx| {
16288        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16289    });
16290    _ = editor.update(cx, |editor, window, cx| {
16291        assert_eq!(
16292            editor.selections.ranges(&editor.display_snapshot(cx)),
16293            [Point::new(0, 0)..Point::new(0, 0)]
16294        );
16295
16296        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16297        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16298        assert_eq!(
16299            editor.selections.ranges(&editor.display_snapshot(cx)),
16300            [Point::new(0, 3)..Point::new(0, 3)]
16301        );
16302        assert!(editor.selections.pending_anchor().is_some());
16303    });
16304}
16305
16306#[gpui::test]
16307async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16308    init_test(cx, |_| {});
16309
16310    let language = Arc::new(
16311        Language::new(
16312            LanguageConfig {
16313                brackets: BracketPairConfig {
16314                    pairs: vec![
16315                        BracketPair {
16316                            start: "{".to_string(),
16317                            end: "}".to_string(),
16318                            close: true,
16319                            surround: true,
16320                            newline: true,
16321                        },
16322                        BracketPair {
16323                            start: "/* ".to_string(),
16324                            end: " */".to_string(),
16325                            close: true,
16326                            surround: true,
16327                            newline: true,
16328                        },
16329                    ],
16330                    ..Default::default()
16331                },
16332                ..Default::default()
16333            },
16334            Some(tree_sitter_rust::LANGUAGE.into()),
16335        )
16336        .with_indents_query("")
16337        .unwrap(),
16338    );
16339
16340    let text = concat!(
16341        "{   }\n",     //
16342        "  x\n",       //
16343        "  /*   */\n", //
16344        "x\n",         //
16345        "{{} }\n",     //
16346    );
16347
16348    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16349    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16350    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16351    editor
16352        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16353        .await;
16354
16355    editor.update_in(cx, |editor, window, cx| {
16356        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16357            s.select_display_ranges([
16358                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16359                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16360                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16361            ])
16362        });
16363        editor.newline(&Newline, window, cx);
16364
16365        assert_eq!(
16366            editor.buffer().read(cx).read(cx).text(),
16367            concat!(
16368                "{ \n",    // Suppress rustfmt
16369                "\n",      //
16370                "}\n",     //
16371                "  x\n",   //
16372                "  /* \n", //
16373                "  \n",    //
16374                "  */\n",  //
16375                "x\n",     //
16376                "{{} \n",  //
16377                "}\n",     //
16378            )
16379        );
16380    });
16381}
16382
16383#[gpui::test]
16384fn test_highlighted_ranges(cx: &mut TestAppContext) {
16385    init_test(cx, |_| {});
16386
16387    let editor = cx.add_window(|window, cx| {
16388        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16389        build_editor(buffer, window, cx)
16390    });
16391
16392    _ = editor.update(cx, |editor, window, cx| {
16393        struct Type1;
16394        struct Type2;
16395
16396        let buffer = editor.buffer.read(cx).snapshot(cx);
16397
16398        let anchor_range =
16399            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16400
16401        editor.highlight_background::<Type1>(
16402            &[
16403                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16404                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16405                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16406                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16407            ],
16408            |_| Hsla::red(),
16409            cx,
16410        );
16411        editor.highlight_background::<Type2>(
16412            &[
16413                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16414                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16415                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16416                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16417            ],
16418            |_| Hsla::green(),
16419            cx,
16420        );
16421
16422        let snapshot = editor.snapshot(window, cx);
16423        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16424            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16425            &snapshot,
16426            cx.theme(),
16427        );
16428        assert_eq!(
16429            highlighted_ranges,
16430            &[
16431                (
16432                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16433                    Hsla::green(),
16434                ),
16435                (
16436                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16437                    Hsla::red(),
16438                ),
16439                (
16440                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16441                    Hsla::green(),
16442                ),
16443                (
16444                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16445                    Hsla::red(),
16446                ),
16447            ]
16448        );
16449        assert_eq!(
16450            editor.sorted_background_highlights_in_range(
16451                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16452                &snapshot,
16453                cx.theme(),
16454            ),
16455            &[(
16456                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16457                Hsla::red(),
16458            )]
16459        );
16460    });
16461}
16462
16463#[gpui::test]
16464async fn test_following(cx: &mut TestAppContext) {
16465    init_test(cx, |_| {});
16466
16467    let fs = FakeFs::new(cx.executor());
16468    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16469
16470    let buffer = project.update(cx, |project, cx| {
16471        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16472        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16473    });
16474    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16475    let follower = cx.update(|cx| {
16476        cx.open_window(
16477            WindowOptions {
16478                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16479                    gpui::Point::new(px(0.), px(0.)),
16480                    gpui::Point::new(px(10.), px(80.)),
16481                ))),
16482                ..Default::default()
16483            },
16484            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16485        )
16486        .unwrap()
16487    });
16488
16489    let is_still_following = Rc::new(RefCell::new(true));
16490    let follower_edit_event_count = Rc::new(RefCell::new(0));
16491    let pending_update = Rc::new(RefCell::new(None));
16492    let leader_entity = leader.root(cx).unwrap();
16493    let follower_entity = follower.root(cx).unwrap();
16494    _ = follower.update(cx, {
16495        let update = pending_update.clone();
16496        let is_still_following = is_still_following.clone();
16497        let follower_edit_event_count = follower_edit_event_count.clone();
16498        |_, window, cx| {
16499            cx.subscribe_in(
16500                &leader_entity,
16501                window,
16502                move |_, leader, event, window, cx| {
16503                    leader.read(cx).add_event_to_update_proto(
16504                        event,
16505                        &mut update.borrow_mut(),
16506                        window,
16507                        cx,
16508                    );
16509                },
16510            )
16511            .detach();
16512
16513            cx.subscribe_in(
16514                &follower_entity,
16515                window,
16516                move |_, _, event: &EditorEvent, _window, _cx| {
16517                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16518                        *is_still_following.borrow_mut() = false;
16519                    }
16520
16521                    if let EditorEvent::BufferEdited = event {
16522                        *follower_edit_event_count.borrow_mut() += 1;
16523                    }
16524                },
16525            )
16526            .detach();
16527        }
16528    });
16529
16530    // Update the selections only
16531    _ = leader.update(cx, |leader, window, cx| {
16532        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16533            s.select_ranges([1..1])
16534        });
16535    });
16536    follower
16537        .update(cx, |follower, window, cx| {
16538            follower.apply_update_proto(
16539                &project,
16540                pending_update.borrow_mut().take().unwrap(),
16541                window,
16542                cx,
16543            )
16544        })
16545        .unwrap()
16546        .await
16547        .unwrap();
16548    _ = follower.update(cx, |follower, _, cx| {
16549        assert_eq!(
16550            follower.selections.ranges(&follower.display_snapshot(cx)),
16551            vec![1..1]
16552        );
16553    });
16554    assert!(*is_still_following.borrow());
16555    assert_eq!(*follower_edit_event_count.borrow(), 0);
16556
16557    // Update the scroll position only
16558    _ = leader.update(cx, |leader, window, cx| {
16559        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16560    });
16561    follower
16562        .update(cx, |follower, window, cx| {
16563            follower.apply_update_proto(
16564                &project,
16565                pending_update.borrow_mut().take().unwrap(),
16566                window,
16567                cx,
16568            )
16569        })
16570        .unwrap()
16571        .await
16572        .unwrap();
16573    assert_eq!(
16574        follower
16575            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16576            .unwrap(),
16577        gpui::Point::new(1.5, 3.5)
16578    );
16579    assert!(*is_still_following.borrow());
16580    assert_eq!(*follower_edit_event_count.borrow(), 0);
16581
16582    // Update the selections and scroll position. The follower's scroll position is updated
16583    // via autoscroll, not via the leader's exact scroll position.
16584    _ = leader.update(cx, |leader, window, cx| {
16585        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16586            s.select_ranges([0..0])
16587        });
16588        leader.request_autoscroll(Autoscroll::newest(), cx);
16589        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16590    });
16591    follower
16592        .update(cx, |follower, window, cx| {
16593            follower.apply_update_proto(
16594                &project,
16595                pending_update.borrow_mut().take().unwrap(),
16596                window,
16597                cx,
16598            )
16599        })
16600        .unwrap()
16601        .await
16602        .unwrap();
16603    _ = follower.update(cx, |follower, _, cx| {
16604        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16605        assert_eq!(
16606            follower.selections.ranges(&follower.display_snapshot(cx)),
16607            vec![0..0]
16608        );
16609    });
16610    assert!(*is_still_following.borrow());
16611
16612    // Creating a pending selection that precedes another selection
16613    _ = leader.update(cx, |leader, window, cx| {
16614        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16615            s.select_ranges([1..1])
16616        });
16617        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16618    });
16619    follower
16620        .update(cx, |follower, window, cx| {
16621            follower.apply_update_proto(
16622                &project,
16623                pending_update.borrow_mut().take().unwrap(),
16624                window,
16625                cx,
16626            )
16627        })
16628        .unwrap()
16629        .await
16630        .unwrap();
16631    _ = follower.update(cx, |follower, _, cx| {
16632        assert_eq!(
16633            follower.selections.ranges(&follower.display_snapshot(cx)),
16634            vec![0..0, 1..1]
16635        );
16636    });
16637    assert!(*is_still_following.borrow());
16638
16639    // Extend the pending selection so that it surrounds another selection
16640    _ = leader.update(cx, |leader, window, cx| {
16641        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16642    });
16643    follower
16644        .update(cx, |follower, window, cx| {
16645            follower.apply_update_proto(
16646                &project,
16647                pending_update.borrow_mut().take().unwrap(),
16648                window,
16649                cx,
16650            )
16651        })
16652        .unwrap()
16653        .await
16654        .unwrap();
16655    _ = follower.update(cx, |follower, _, cx| {
16656        assert_eq!(
16657            follower.selections.ranges(&follower.display_snapshot(cx)),
16658            vec![0..2]
16659        );
16660    });
16661
16662    // Scrolling locally breaks the follow
16663    _ = follower.update(cx, |follower, window, cx| {
16664        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16665        follower.set_scroll_anchor(
16666            ScrollAnchor {
16667                anchor: top_anchor,
16668                offset: gpui::Point::new(0.0, 0.5),
16669            },
16670            window,
16671            cx,
16672        );
16673    });
16674    assert!(!(*is_still_following.borrow()));
16675}
16676
16677#[gpui::test]
16678async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16679    init_test(cx, |_| {});
16680
16681    let fs = FakeFs::new(cx.executor());
16682    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16683    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16684    let pane = workspace
16685        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16686        .unwrap();
16687
16688    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16689
16690    let leader = pane.update_in(cx, |_, window, cx| {
16691        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16692        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16693    });
16694
16695    // Start following the editor when it has no excerpts.
16696    let mut state_message =
16697        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16698    let workspace_entity = workspace.root(cx).unwrap();
16699    let follower_1 = cx
16700        .update_window(*workspace.deref(), |_, window, cx| {
16701            Editor::from_state_proto(
16702                workspace_entity,
16703                ViewId {
16704                    creator: CollaboratorId::PeerId(PeerId::default()),
16705                    id: 0,
16706                },
16707                &mut state_message,
16708                window,
16709                cx,
16710            )
16711        })
16712        .unwrap()
16713        .unwrap()
16714        .await
16715        .unwrap();
16716
16717    let update_message = Rc::new(RefCell::new(None));
16718    follower_1.update_in(cx, {
16719        let update = update_message.clone();
16720        |_, window, cx| {
16721            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16722                leader.read(cx).add_event_to_update_proto(
16723                    event,
16724                    &mut update.borrow_mut(),
16725                    window,
16726                    cx,
16727                );
16728            })
16729            .detach();
16730        }
16731    });
16732
16733    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16734        (
16735            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16736            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16737        )
16738    });
16739
16740    // Insert some excerpts.
16741    leader.update(cx, |leader, cx| {
16742        leader.buffer.update(cx, |multibuffer, cx| {
16743            multibuffer.set_excerpts_for_path(
16744                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16745                buffer_1.clone(),
16746                vec![
16747                    Point::row_range(0..3),
16748                    Point::row_range(1..6),
16749                    Point::row_range(12..15),
16750                ],
16751                0,
16752                cx,
16753            );
16754            multibuffer.set_excerpts_for_path(
16755                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16756                buffer_2.clone(),
16757                vec![Point::row_range(0..6), Point::row_range(8..12)],
16758                0,
16759                cx,
16760            );
16761        });
16762    });
16763
16764    // Apply the update of adding the excerpts.
16765    follower_1
16766        .update_in(cx, |follower, window, cx| {
16767            follower.apply_update_proto(
16768                &project,
16769                update_message.borrow().clone().unwrap(),
16770                window,
16771                cx,
16772            )
16773        })
16774        .await
16775        .unwrap();
16776    assert_eq!(
16777        follower_1.update(cx, |editor, cx| editor.text(cx)),
16778        leader.update(cx, |editor, cx| editor.text(cx))
16779    );
16780    update_message.borrow_mut().take();
16781
16782    // Start following separately after it already has excerpts.
16783    let mut state_message =
16784        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16785    let workspace_entity = workspace.root(cx).unwrap();
16786    let follower_2 = cx
16787        .update_window(*workspace.deref(), |_, window, cx| {
16788            Editor::from_state_proto(
16789                workspace_entity,
16790                ViewId {
16791                    creator: CollaboratorId::PeerId(PeerId::default()),
16792                    id: 0,
16793                },
16794                &mut state_message,
16795                window,
16796                cx,
16797            )
16798        })
16799        .unwrap()
16800        .unwrap()
16801        .await
16802        .unwrap();
16803    assert_eq!(
16804        follower_2.update(cx, |editor, cx| editor.text(cx)),
16805        leader.update(cx, |editor, cx| editor.text(cx))
16806    );
16807
16808    // Remove some excerpts.
16809    leader.update(cx, |leader, cx| {
16810        leader.buffer.update(cx, |multibuffer, cx| {
16811            let excerpt_ids = multibuffer.excerpt_ids();
16812            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16813            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16814        });
16815    });
16816
16817    // Apply the update of removing the excerpts.
16818    follower_1
16819        .update_in(cx, |follower, window, cx| {
16820            follower.apply_update_proto(
16821                &project,
16822                update_message.borrow().clone().unwrap(),
16823                window,
16824                cx,
16825            )
16826        })
16827        .await
16828        .unwrap();
16829    follower_2
16830        .update_in(cx, |follower, window, cx| {
16831            follower.apply_update_proto(
16832                &project,
16833                update_message.borrow().clone().unwrap(),
16834                window,
16835                cx,
16836            )
16837        })
16838        .await
16839        .unwrap();
16840    update_message.borrow_mut().take();
16841    assert_eq!(
16842        follower_1.update(cx, |editor, cx| editor.text(cx)),
16843        leader.update(cx, |editor, cx| editor.text(cx))
16844    );
16845}
16846
16847#[gpui::test]
16848async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16849    init_test(cx, |_| {});
16850
16851    let mut cx = EditorTestContext::new(cx).await;
16852    let lsp_store =
16853        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16854
16855    cx.set_state(indoc! {"
16856        ˇfn func(abc def: i32) -> u32 {
16857        }
16858    "});
16859
16860    cx.update(|_, cx| {
16861        lsp_store.update(cx, |lsp_store, cx| {
16862            lsp_store
16863                .update_diagnostics(
16864                    LanguageServerId(0),
16865                    lsp::PublishDiagnosticsParams {
16866                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16867                        version: None,
16868                        diagnostics: vec![
16869                            lsp::Diagnostic {
16870                                range: lsp::Range::new(
16871                                    lsp::Position::new(0, 11),
16872                                    lsp::Position::new(0, 12),
16873                                ),
16874                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16875                                ..Default::default()
16876                            },
16877                            lsp::Diagnostic {
16878                                range: lsp::Range::new(
16879                                    lsp::Position::new(0, 12),
16880                                    lsp::Position::new(0, 15),
16881                                ),
16882                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16883                                ..Default::default()
16884                            },
16885                            lsp::Diagnostic {
16886                                range: lsp::Range::new(
16887                                    lsp::Position::new(0, 25),
16888                                    lsp::Position::new(0, 28),
16889                                ),
16890                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16891                                ..Default::default()
16892                            },
16893                        ],
16894                    },
16895                    None,
16896                    DiagnosticSourceKind::Pushed,
16897                    &[],
16898                    cx,
16899                )
16900                .unwrap()
16901        });
16902    });
16903
16904    executor.run_until_parked();
16905
16906    cx.update_editor(|editor, window, cx| {
16907        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16908    });
16909
16910    cx.assert_editor_state(indoc! {"
16911        fn func(abc def: i32) -> ˇu32 {
16912        }
16913    "});
16914
16915    cx.update_editor(|editor, window, cx| {
16916        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16917    });
16918
16919    cx.assert_editor_state(indoc! {"
16920        fn func(abc ˇdef: i32) -> u32 {
16921        }
16922    "});
16923
16924    cx.update_editor(|editor, window, cx| {
16925        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16926    });
16927
16928    cx.assert_editor_state(indoc! {"
16929        fn func(abcˇ def: i32) -> u32 {
16930        }
16931    "});
16932
16933    cx.update_editor(|editor, window, cx| {
16934        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16935    });
16936
16937    cx.assert_editor_state(indoc! {"
16938        fn func(abc def: i32) -> ˇu32 {
16939        }
16940    "});
16941}
16942
16943#[gpui::test]
16944async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16945    init_test(cx, |_| {});
16946
16947    let mut cx = EditorTestContext::new(cx).await;
16948
16949    let diff_base = r#"
16950        use some::mod;
16951
16952        const A: u32 = 42;
16953
16954        fn main() {
16955            println!("hello");
16956
16957            println!("world");
16958        }
16959        "#
16960    .unindent();
16961
16962    // Edits are modified, removed, modified, added
16963    cx.set_state(
16964        &r#"
16965        use some::modified;
16966
16967        ˇ
16968        fn main() {
16969            println!("hello there");
16970
16971            println!("around the");
16972            println!("world");
16973        }
16974        "#
16975        .unindent(),
16976    );
16977
16978    cx.set_head_text(&diff_base);
16979    executor.run_until_parked();
16980
16981    cx.update_editor(|editor, window, cx| {
16982        //Wrap around the bottom of the buffer
16983        for _ in 0..3 {
16984            editor.go_to_next_hunk(&GoToHunk, window, cx);
16985        }
16986    });
16987
16988    cx.assert_editor_state(
16989        &r#"
16990        ˇuse some::modified;
16991
16992
16993        fn main() {
16994            println!("hello there");
16995
16996            println!("around the");
16997            println!("world");
16998        }
16999        "#
17000        .unindent(),
17001    );
17002
17003    cx.update_editor(|editor, window, cx| {
17004        //Wrap around the top of the buffer
17005        for _ in 0..2 {
17006            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17007        }
17008    });
17009
17010    cx.assert_editor_state(
17011        &r#"
17012        use some::modified;
17013
17014
17015        fn main() {
17016        ˇ    println!("hello there");
17017
17018            println!("around the");
17019            println!("world");
17020        }
17021        "#
17022        .unindent(),
17023    );
17024
17025    cx.update_editor(|editor, window, cx| {
17026        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17027    });
17028
17029    cx.assert_editor_state(
17030        &r#"
17031        use some::modified;
17032
17033        ˇ
17034        fn main() {
17035            println!("hello there");
17036
17037            println!("around the");
17038            println!("world");
17039        }
17040        "#
17041        .unindent(),
17042    );
17043
17044    cx.update_editor(|editor, window, cx| {
17045        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17046    });
17047
17048    cx.assert_editor_state(
17049        &r#"
17050        ˇuse some::modified;
17051
17052
17053        fn main() {
17054            println!("hello there");
17055
17056            println!("around the");
17057            println!("world");
17058        }
17059        "#
17060        .unindent(),
17061    );
17062
17063    cx.update_editor(|editor, window, cx| {
17064        for _ in 0..2 {
17065            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17066        }
17067    });
17068
17069    cx.assert_editor_state(
17070        &r#"
17071        use some::modified;
17072
17073
17074        fn main() {
17075        ˇ    println!("hello there");
17076
17077            println!("around the");
17078            println!("world");
17079        }
17080        "#
17081        .unindent(),
17082    );
17083
17084    cx.update_editor(|editor, window, cx| {
17085        editor.fold(&Fold, window, cx);
17086    });
17087
17088    cx.update_editor(|editor, window, cx| {
17089        editor.go_to_next_hunk(&GoToHunk, window, cx);
17090    });
17091
17092    cx.assert_editor_state(
17093        &r#"
17094        ˇuse some::modified;
17095
17096
17097        fn main() {
17098            println!("hello there");
17099
17100            println!("around the");
17101            println!("world");
17102        }
17103        "#
17104        .unindent(),
17105    );
17106}
17107
17108#[test]
17109fn test_split_words() {
17110    fn split(text: &str) -> Vec<&str> {
17111        split_words(text).collect()
17112    }
17113
17114    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17115    assert_eq!(split("hello_world"), &["hello_", "world"]);
17116    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17117    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17118    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17119    assert_eq!(split("helloworld"), &["helloworld"]);
17120
17121    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17122}
17123
17124#[gpui::test]
17125async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17126    init_test(cx, |_| {});
17127
17128    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17129    let mut assert = |before, after| {
17130        let _state_context = cx.set_state(before);
17131        cx.run_until_parked();
17132        cx.update_editor(|editor, window, cx| {
17133            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17134        });
17135        cx.run_until_parked();
17136        cx.assert_editor_state(after);
17137    };
17138
17139    // Outside bracket jumps to outside of matching bracket
17140    assert("console.logˇ(var);", "console.log(var)ˇ;");
17141    assert("console.log(var)ˇ;", "console.logˇ(var);");
17142
17143    // Inside bracket jumps to inside of matching bracket
17144    assert("console.log(ˇvar);", "console.log(varˇ);");
17145    assert("console.log(varˇ);", "console.log(ˇvar);");
17146
17147    // When outside a bracket and inside, favor jumping to the inside bracket
17148    assert(
17149        "console.log('foo', [1, 2, 3]ˇ);",
17150        "console.log(ˇ'foo', [1, 2, 3]);",
17151    );
17152    assert(
17153        "console.log(ˇ'foo', [1, 2, 3]);",
17154        "console.log('foo', [1, 2, 3]ˇ);",
17155    );
17156
17157    // Bias forward if two options are equally likely
17158    assert(
17159        "let result = curried_fun()ˇ();",
17160        "let result = curried_fun()()ˇ;",
17161    );
17162
17163    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17164    assert(
17165        indoc! {"
17166            function test() {
17167                console.log('test')ˇ
17168            }"},
17169        indoc! {"
17170            function test() {
17171                console.logˇ('test')
17172            }"},
17173    );
17174}
17175
17176#[gpui::test]
17177async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17178    init_test(cx, |_| {});
17179
17180    let fs = FakeFs::new(cx.executor());
17181    fs.insert_tree(
17182        path!("/a"),
17183        json!({
17184            "main.rs": "fn main() { let a = 5; }",
17185            "other.rs": "// Test file",
17186        }),
17187    )
17188    .await;
17189    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17190
17191    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17192    language_registry.add(Arc::new(Language::new(
17193        LanguageConfig {
17194            name: "Rust".into(),
17195            matcher: LanguageMatcher {
17196                path_suffixes: vec!["rs".to_string()],
17197                ..Default::default()
17198            },
17199            brackets: BracketPairConfig {
17200                pairs: vec![BracketPair {
17201                    start: "{".to_string(),
17202                    end: "}".to_string(),
17203                    close: true,
17204                    surround: true,
17205                    newline: true,
17206                }],
17207                disabled_scopes_by_bracket_ix: Vec::new(),
17208            },
17209            ..Default::default()
17210        },
17211        Some(tree_sitter_rust::LANGUAGE.into()),
17212    )));
17213    let mut fake_servers = language_registry.register_fake_lsp(
17214        "Rust",
17215        FakeLspAdapter {
17216            capabilities: lsp::ServerCapabilities {
17217                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17218                    first_trigger_character: "{".to_string(),
17219                    more_trigger_character: None,
17220                }),
17221                ..Default::default()
17222            },
17223            ..Default::default()
17224        },
17225    );
17226
17227    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17228
17229    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17230
17231    let worktree_id = workspace
17232        .update(cx, |workspace, _, cx| {
17233            workspace.project().update(cx, |project, cx| {
17234                project.worktrees(cx).next().unwrap().read(cx).id()
17235            })
17236        })
17237        .unwrap();
17238
17239    let buffer = project
17240        .update(cx, |project, cx| {
17241            project.open_local_buffer(path!("/a/main.rs"), cx)
17242        })
17243        .await
17244        .unwrap();
17245    let editor_handle = workspace
17246        .update(cx, |workspace, window, cx| {
17247            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17248        })
17249        .unwrap()
17250        .await
17251        .unwrap()
17252        .downcast::<Editor>()
17253        .unwrap();
17254
17255    cx.executor().start_waiting();
17256    let fake_server = fake_servers.next().await.unwrap();
17257
17258    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17259        |params, _| async move {
17260            assert_eq!(
17261                params.text_document_position.text_document.uri,
17262                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17263            );
17264            assert_eq!(
17265                params.text_document_position.position,
17266                lsp::Position::new(0, 21),
17267            );
17268
17269            Ok(Some(vec![lsp::TextEdit {
17270                new_text: "]".to_string(),
17271                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17272            }]))
17273        },
17274    );
17275
17276    editor_handle.update_in(cx, |editor, window, cx| {
17277        window.focus(&editor.focus_handle(cx));
17278        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17279            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17280        });
17281        editor.handle_input("{", window, cx);
17282    });
17283
17284    cx.executor().run_until_parked();
17285
17286    buffer.update(cx, |buffer, _| {
17287        assert_eq!(
17288            buffer.text(),
17289            "fn main() { let a = {5}; }",
17290            "No extra braces from on type formatting should appear in the buffer"
17291        )
17292    });
17293}
17294
17295#[gpui::test(iterations = 20, seeds(31))]
17296async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17297    init_test(cx, |_| {});
17298
17299    let mut cx = EditorLspTestContext::new_rust(
17300        lsp::ServerCapabilities {
17301            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17302                first_trigger_character: ".".to_string(),
17303                more_trigger_character: None,
17304            }),
17305            ..Default::default()
17306        },
17307        cx,
17308    )
17309    .await;
17310
17311    cx.update_buffer(|buffer, _| {
17312        // This causes autoindent to be async.
17313        buffer.set_sync_parse_timeout(Duration::ZERO)
17314    });
17315
17316    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17317    cx.simulate_keystroke("\n");
17318    cx.run_until_parked();
17319
17320    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17321    let mut request =
17322        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17323            let buffer_cloned = buffer_cloned.clone();
17324            async move {
17325                buffer_cloned.update(&mut cx, |buffer, _| {
17326                    assert_eq!(
17327                        buffer.text(),
17328                        "fn c() {\n    d()\n        .\n}\n",
17329                        "OnTypeFormatting should triggered after autoindent applied"
17330                    )
17331                })?;
17332
17333                Ok(Some(vec![]))
17334            }
17335        });
17336
17337    cx.simulate_keystroke(".");
17338    cx.run_until_parked();
17339
17340    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17341    assert!(request.next().await.is_some());
17342    request.close();
17343    assert!(request.next().await.is_none());
17344}
17345
17346#[gpui::test]
17347async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17348    init_test(cx, |_| {});
17349
17350    let fs = FakeFs::new(cx.executor());
17351    fs.insert_tree(
17352        path!("/a"),
17353        json!({
17354            "main.rs": "fn main() { let a = 5; }",
17355            "other.rs": "// Test file",
17356        }),
17357    )
17358    .await;
17359
17360    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17361
17362    let server_restarts = Arc::new(AtomicUsize::new(0));
17363    let closure_restarts = Arc::clone(&server_restarts);
17364    let language_server_name = "test language server";
17365    let language_name: LanguageName = "Rust".into();
17366
17367    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17368    language_registry.add(Arc::new(Language::new(
17369        LanguageConfig {
17370            name: language_name.clone(),
17371            matcher: LanguageMatcher {
17372                path_suffixes: vec!["rs".to_string()],
17373                ..Default::default()
17374            },
17375            ..Default::default()
17376        },
17377        Some(tree_sitter_rust::LANGUAGE.into()),
17378    )));
17379    let mut fake_servers = language_registry.register_fake_lsp(
17380        "Rust",
17381        FakeLspAdapter {
17382            name: language_server_name,
17383            initialization_options: Some(json!({
17384                "testOptionValue": true
17385            })),
17386            initializer: Some(Box::new(move |fake_server| {
17387                let task_restarts = Arc::clone(&closure_restarts);
17388                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17389                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17390                    futures::future::ready(Ok(()))
17391                });
17392            })),
17393            ..Default::default()
17394        },
17395    );
17396
17397    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17398    let _buffer = project
17399        .update(cx, |project, cx| {
17400            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17401        })
17402        .await
17403        .unwrap();
17404    let _fake_server = fake_servers.next().await.unwrap();
17405    update_test_language_settings(cx, |language_settings| {
17406        language_settings.languages.0.insert(
17407            language_name.clone().0,
17408            LanguageSettingsContent {
17409                tab_size: NonZeroU32::new(8),
17410                ..Default::default()
17411            },
17412        );
17413    });
17414    cx.executor().run_until_parked();
17415    assert_eq!(
17416        server_restarts.load(atomic::Ordering::Acquire),
17417        0,
17418        "Should not restart LSP server on an unrelated change"
17419    );
17420
17421    update_test_project_settings(cx, |project_settings| {
17422        project_settings.lsp.insert(
17423            "Some other server name".into(),
17424            LspSettings {
17425                binary: None,
17426                settings: None,
17427                initialization_options: Some(json!({
17428                    "some other init value": false
17429                })),
17430                enable_lsp_tasks: false,
17431                fetch: None,
17432            },
17433        );
17434    });
17435    cx.executor().run_until_parked();
17436    assert_eq!(
17437        server_restarts.load(atomic::Ordering::Acquire),
17438        0,
17439        "Should not restart LSP server on an unrelated LSP settings change"
17440    );
17441
17442    update_test_project_settings(cx, |project_settings| {
17443        project_settings.lsp.insert(
17444            language_server_name.into(),
17445            LspSettings {
17446                binary: None,
17447                settings: None,
17448                initialization_options: Some(json!({
17449                    "anotherInitValue": false
17450                })),
17451                enable_lsp_tasks: false,
17452                fetch: None,
17453            },
17454        );
17455    });
17456    cx.executor().run_until_parked();
17457    assert_eq!(
17458        server_restarts.load(atomic::Ordering::Acquire),
17459        1,
17460        "Should restart LSP server on a related LSP settings change"
17461    );
17462
17463    update_test_project_settings(cx, |project_settings| {
17464        project_settings.lsp.insert(
17465            language_server_name.into(),
17466            LspSettings {
17467                binary: None,
17468                settings: None,
17469                initialization_options: Some(json!({
17470                    "anotherInitValue": false
17471                })),
17472                enable_lsp_tasks: false,
17473                fetch: None,
17474            },
17475        );
17476    });
17477    cx.executor().run_until_parked();
17478    assert_eq!(
17479        server_restarts.load(atomic::Ordering::Acquire),
17480        1,
17481        "Should not restart LSP server on a related LSP settings change that is the same"
17482    );
17483
17484    update_test_project_settings(cx, |project_settings| {
17485        project_settings.lsp.insert(
17486            language_server_name.into(),
17487            LspSettings {
17488                binary: None,
17489                settings: None,
17490                initialization_options: None,
17491                enable_lsp_tasks: false,
17492                fetch: None,
17493            },
17494        );
17495    });
17496    cx.executor().run_until_parked();
17497    assert_eq!(
17498        server_restarts.load(atomic::Ordering::Acquire),
17499        2,
17500        "Should restart LSP server on another related LSP settings change"
17501    );
17502}
17503
17504#[gpui::test]
17505async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17506    init_test(cx, |_| {});
17507
17508    let mut cx = EditorLspTestContext::new_rust(
17509        lsp::ServerCapabilities {
17510            completion_provider: Some(lsp::CompletionOptions {
17511                trigger_characters: Some(vec![".".to_string()]),
17512                resolve_provider: Some(true),
17513                ..Default::default()
17514            }),
17515            ..Default::default()
17516        },
17517        cx,
17518    )
17519    .await;
17520
17521    cx.set_state("fn main() { let a = 2ˇ; }");
17522    cx.simulate_keystroke(".");
17523    let completion_item = lsp::CompletionItem {
17524        label: "some".into(),
17525        kind: Some(lsp::CompletionItemKind::SNIPPET),
17526        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17527        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17528            kind: lsp::MarkupKind::Markdown,
17529            value: "```rust\nSome(2)\n```".to_string(),
17530        })),
17531        deprecated: Some(false),
17532        sort_text: Some("fffffff2".to_string()),
17533        filter_text: Some("some".to_string()),
17534        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17535        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17536            range: lsp::Range {
17537                start: lsp::Position {
17538                    line: 0,
17539                    character: 22,
17540                },
17541                end: lsp::Position {
17542                    line: 0,
17543                    character: 22,
17544                },
17545            },
17546            new_text: "Some(2)".to_string(),
17547        })),
17548        additional_text_edits: Some(vec![lsp::TextEdit {
17549            range: lsp::Range {
17550                start: lsp::Position {
17551                    line: 0,
17552                    character: 20,
17553                },
17554                end: lsp::Position {
17555                    line: 0,
17556                    character: 22,
17557                },
17558            },
17559            new_text: "".to_string(),
17560        }]),
17561        ..Default::default()
17562    };
17563
17564    let closure_completion_item = completion_item.clone();
17565    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17566        let task_completion_item = closure_completion_item.clone();
17567        async move {
17568            Ok(Some(lsp::CompletionResponse::Array(vec![
17569                task_completion_item,
17570            ])))
17571        }
17572    });
17573
17574    request.next().await;
17575
17576    cx.condition(|editor, _| editor.context_menu_visible())
17577        .await;
17578    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17579        editor
17580            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17581            .unwrap()
17582    });
17583    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17584
17585    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17586        let task_completion_item = completion_item.clone();
17587        async move { Ok(task_completion_item) }
17588    })
17589    .next()
17590    .await
17591    .unwrap();
17592    apply_additional_edits.await.unwrap();
17593    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17594}
17595
17596#[gpui::test]
17597async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17598    init_test(cx, |_| {});
17599
17600    let mut cx = EditorLspTestContext::new_rust(
17601        lsp::ServerCapabilities {
17602            completion_provider: Some(lsp::CompletionOptions {
17603                trigger_characters: Some(vec![".".to_string()]),
17604                resolve_provider: Some(true),
17605                ..Default::default()
17606            }),
17607            ..Default::default()
17608        },
17609        cx,
17610    )
17611    .await;
17612
17613    cx.set_state("fn main() { let a = 2ˇ; }");
17614    cx.simulate_keystroke(".");
17615
17616    let item1 = lsp::CompletionItem {
17617        label: "method id()".to_string(),
17618        filter_text: Some("id".to_string()),
17619        detail: None,
17620        documentation: None,
17621        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17622            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17623            new_text: ".id".to_string(),
17624        })),
17625        ..lsp::CompletionItem::default()
17626    };
17627
17628    let item2 = lsp::CompletionItem {
17629        label: "other".to_string(),
17630        filter_text: Some("other".to_string()),
17631        detail: None,
17632        documentation: None,
17633        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17634            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17635            new_text: ".other".to_string(),
17636        })),
17637        ..lsp::CompletionItem::default()
17638    };
17639
17640    let item1 = item1.clone();
17641    cx.set_request_handler::<lsp::request::Completion, _, _>({
17642        let item1 = item1.clone();
17643        move |_, _, _| {
17644            let item1 = item1.clone();
17645            let item2 = item2.clone();
17646            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17647        }
17648    })
17649    .next()
17650    .await;
17651
17652    cx.condition(|editor, _| editor.context_menu_visible())
17653        .await;
17654    cx.update_editor(|editor, _, _| {
17655        let context_menu = editor.context_menu.borrow_mut();
17656        let context_menu = context_menu
17657            .as_ref()
17658            .expect("Should have the context menu deployed");
17659        match context_menu {
17660            CodeContextMenu::Completions(completions_menu) => {
17661                let completions = completions_menu.completions.borrow_mut();
17662                assert_eq!(
17663                    completions
17664                        .iter()
17665                        .map(|completion| &completion.label.text)
17666                        .collect::<Vec<_>>(),
17667                    vec!["method id()", "other"]
17668                )
17669            }
17670            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17671        }
17672    });
17673
17674    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17675        let item1 = item1.clone();
17676        move |_, item_to_resolve, _| {
17677            let item1 = item1.clone();
17678            async move {
17679                if item1 == item_to_resolve {
17680                    Ok(lsp::CompletionItem {
17681                        label: "method id()".to_string(),
17682                        filter_text: Some("id".to_string()),
17683                        detail: Some("Now resolved!".to_string()),
17684                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17685                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17686                            range: lsp::Range::new(
17687                                lsp::Position::new(0, 22),
17688                                lsp::Position::new(0, 22),
17689                            ),
17690                            new_text: ".id".to_string(),
17691                        })),
17692                        ..lsp::CompletionItem::default()
17693                    })
17694                } else {
17695                    Ok(item_to_resolve)
17696                }
17697            }
17698        }
17699    })
17700    .next()
17701    .await
17702    .unwrap();
17703    cx.run_until_parked();
17704
17705    cx.update_editor(|editor, window, cx| {
17706        editor.context_menu_next(&Default::default(), window, cx);
17707    });
17708
17709    cx.update_editor(|editor, _, _| {
17710        let context_menu = editor.context_menu.borrow_mut();
17711        let context_menu = context_menu
17712            .as_ref()
17713            .expect("Should have the context menu deployed");
17714        match context_menu {
17715            CodeContextMenu::Completions(completions_menu) => {
17716                let completions = completions_menu.completions.borrow_mut();
17717                assert_eq!(
17718                    completions
17719                        .iter()
17720                        .map(|completion| &completion.label.text)
17721                        .collect::<Vec<_>>(),
17722                    vec!["method id() Now resolved!", "other"],
17723                    "Should update first completion label, but not second as the filter text did not match."
17724                );
17725            }
17726            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17727        }
17728    });
17729}
17730
17731#[gpui::test]
17732async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17733    init_test(cx, |_| {});
17734    let mut cx = EditorLspTestContext::new_rust(
17735        lsp::ServerCapabilities {
17736            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17737            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17738            completion_provider: Some(lsp::CompletionOptions {
17739                resolve_provider: Some(true),
17740                ..Default::default()
17741            }),
17742            ..Default::default()
17743        },
17744        cx,
17745    )
17746    .await;
17747    cx.set_state(indoc! {"
17748        struct TestStruct {
17749            field: i32
17750        }
17751
17752        fn mainˇ() {
17753            let unused_var = 42;
17754            let test_struct = TestStruct { field: 42 };
17755        }
17756    "});
17757    let symbol_range = cx.lsp_range(indoc! {"
17758        struct TestStruct {
17759            field: i32
17760        }
17761
17762        «fn main»() {
17763            let unused_var = 42;
17764            let test_struct = TestStruct { field: 42 };
17765        }
17766    "});
17767    let mut hover_requests =
17768        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17769            Ok(Some(lsp::Hover {
17770                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17771                    kind: lsp::MarkupKind::Markdown,
17772                    value: "Function documentation".to_string(),
17773                }),
17774                range: Some(symbol_range),
17775            }))
17776        });
17777
17778    // Case 1: Test that code action menu hide hover popover
17779    cx.dispatch_action(Hover);
17780    hover_requests.next().await;
17781    cx.condition(|editor, _| editor.hover_state.visible()).await;
17782    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17783        move |_, _, _| async move {
17784            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17785                lsp::CodeAction {
17786                    title: "Remove unused variable".to_string(),
17787                    kind: Some(CodeActionKind::QUICKFIX),
17788                    edit: Some(lsp::WorkspaceEdit {
17789                        changes: Some(
17790                            [(
17791                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17792                                vec![lsp::TextEdit {
17793                                    range: lsp::Range::new(
17794                                        lsp::Position::new(5, 4),
17795                                        lsp::Position::new(5, 27),
17796                                    ),
17797                                    new_text: "".to_string(),
17798                                }],
17799                            )]
17800                            .into_iter()
17801                            .collect(),
17802                        ),
17803                        ..Default::default()
17804                    }),
17805                    ..Default::default()
17806                },
17807            )]))
17808        },
17809    );
17810    cx.update_editor(|editor, window, cx| {
17811        editor.toggle_code_actions(
17812            &ToggleCodeActions {
17813                deployed_from: None,
17814                quick_launch: false,
17815            },
17816            window,
17817            cx,
17818        );
17819    });
17820    code_action_requests.next().await;
17821    cx.run_until_parked();
17822    cx.condition(|editor, _| editor.context_menu_visible())
17823        .await;
17824    cx.update_editor(|editor, _, _| {
17825        assert!(
17826            !editor.hover_state.visible(),
17827            "Hover popover should be hidden when code action menu is shown"
17828        );
17829        // Hide code actions
17830        editor.context_menu.take();
17831    });
17832
17833    // Case 2: Test that code completions hide hover popover
17834    cx.dispatch_action(Hover);
17835    hover_requests.next().await;
17836    cx.condition(|editor, _| editor.hover_state.visible()).await;
17837    let counter = Arc::new(AtomicUsize::new(0));
17838    let mut completion_requests =
17839        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17840            let counter = counter.clone();
17841            async move {
17842                counter.fetch_add(1, atomic::Ordering::Release);
17843                Ok(Some(lsp::CompletionResponse::Array(vec![
17844                    lsp::CompletionItem {
17845                        label: "main".into(),
17846                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17847                        detail: Some("() -> ()".to_string()),
17848                        ..Default::default()
17849                    },
17850                    lsp::CompletionItem {
17851                        label: "TestStruct".into(),
17852                        kind: Some(lsp::CompletionItemKind::STRUCT),
17853                        detail: Some("struct TestStruct".to_string()),
17854                        ..Default::default()
17855                    },
17856                ])))
17857            }
17858        });
17859    cx.update_editor(|editor, window, cx| {
17860        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17861    });
17862    completion_requests.next().await;
17863    cx.condition(|editor, _| editor.context_menu_visible())
17864        .await;
17865    cx.update_editor(|editor, _, _| {
17866        assert!(
17867            !editor.hover_state.visible(),
17868            "Hover popover should be hidden when completion menu is shown"
17869        );
17870    });
17871}
17872
17873#[gpui::test]
17874async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17875    init_test(cx, |_| {});
17876
17877    let mut cx = EditorLspTestContext::new_rust(
17878        lsp::ServerCapabilities {
17879            completion_provider: Some(lsp::CompletionOptions {
17880                trigger_characters: Some(vec![".".to_string()]),
17881                resolve_provider: Some(true),
17882                ..Default::default()
17883            }),
17884            ..Default::default()
17885        },
17886        cx,
17887    )
17888    .await;
17889
17890    cx.set_state("fn main() { let a = 2ˇ; }");
17891    cx.simulate_keystroke(".");
17892
17893    let unresolved_item_1 = lsp::CompletionItem {
17894        label: "id".to_string(),
17895        filter_text: Some("id".to_string()),
17896        detail: None,
17897        documentation: None,
17898        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17899            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17900            new_text: ".id".to_string(),
17901        })),
17902        ..lsp::CompletionItem::default()
17903    };
17904    let resolved_item_1 = lsp::CompletionItem {
17905        additional_text_edits: Some(vec![lsp::TextEdit {
17906            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17907            new_text: "!!".to_string(),
17908        }]),
17909        ..unresolved_item_1.clone()
17910    };
17911    let unresolved_item_2 = lsp::CompletionItem {
17912        label: "other".to_string(),
17913        filter_text: Some("other".to_string()),
17914        detail: None,
17915        documentation: None,
17916        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17917            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17918            new_text: ".other".to_string(),
17919        })),
17920        ..lsp::CompletionItem::default()
17921    };
17922    let resolved_item_2 = lsp::CompletionItem {
17923        additional_text_edits: Some(vec![lsp::TextEdit {
17924            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17925            new_text: "??".to_string(),
17926        }]),
17927        ..unresolved_item_2.clone()
17928    };
17929
17930    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17931    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17932    cx.lsp
17933        .server
17934        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17935            let unresolved_item_1 = unresolved_item_1.clone();
17936            let resolved_item_1 = resolved_item_1.clone();
17937            let unresolved_item_2 = unresolved_item_2.clone();
17938            let resolved_item_2 = resolved_item_2.clone();
17939            let resolve_requests_1 = resolve_requests_1.clone();
17940            let resolve_requests_2 = resolve_requests_2.clone();
17941            move |unresolved_request, _| {
17942                let unresolved_item_1 = unresolved_item_1.clone();
17943                let resolved_item_1 = resolved_item_1.clone();
17944                let unresolved_item_2 = unresolved_item_2.clone();
17945                let resolved_item_2 = resolved_item_2.clone();
17946                let resolve_requests_1 = resolve_requests_1.clone();
17947                let resolve_requests_2 = resolve_requests_2.clone();
17948                async move {
17949                    if unresolved_request == unresolved_item_1 {
17950                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17951                        Ok(resolved_item_1.clone())
17952                    } else if unresolved_request == unresolved_item_2 {
17953                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17954                        Ok(resolved_item_2.clone())
17955                    } else {
17956                        panic!("Unexpected completion item {unresolved_request:?}")
17957                    }
17958                }
17959            }
17960        })
17961        .detach();
17962
17963    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17964        let unresolved_item_1 = unresolved_item_1.clone();
17965        let unresolved_item_2 = unresolved_item_2.clone();
17966        async move {
17967            Ok(Some(lsp::CompletionResponse::Array(vec![
17968                unresolved_item_1,
17969                unresolved_item_2,
17970            ])))
17971        }
17972    })
17973    .next()
17974    .await;
17975
17976    cx.condition(|editor, _| editor.context_menu_visible())
17977        .await;
17978    cx.update_editor(|editor, _, _| {
17979        let context_menu = editor.context_menu.borrow_mut();
17980        let context_menu = context_menu
17981            .as_ref()
17982            .expect("Should have the context menu deployed");
17983        match context_menu {
17984            CodeContextMenu::Completions(completions_menu) => {
17985                let completions = completions_menu.completions.borrow_mut();
17986                assert_eq!(
17987                    completions
17988                        .iter()
17989                        .map(|completion| &completion.label.text)
17990                        .collect::<Vec<_>>(),
17991                    vec!["id", "other"]
17992                )
17993            }
17994            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17995        }
17996    });
17997    cx.run_until_parked();
17998
17999    cx.update_editor(|editor, window, cx| {
18000        editor.context_menu_next(&ContextMenuNext, window, cx);
18001    });
18002    cx.run_until_parked();
18003    cx.update_editor(|editor, window, cx| {
18004        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18005    });
18006    cx.run_until_parked();
18007    cx.update_editor(|editor, window, cx| {
18008        editor.context_menu_next(&ContextMenuNext, window, cx);
18009    });
18010    cx.run_until_parked();
18011    cx.update_editor(|editor, window, cx| {
18012        editor
18013            .compose_completion(&ComposeCompletion::default(), window, cx)
18014            .expect("No task returned")
18015    })
18016    .await
18017    .expect("Completion failed");
18018    cx.run_until_parked();
18019
18020    cx.update_editor(|editor, _, cx| {
18021        assert_eq!(
18022            resolve_requests_1.load(atomic::Ordering::Acquire),
18023            1,
18024            "Should always resolve once despite multiple selections"
18025        );
18026        assert_eq!(
18027            resolve_requests_2.load(atomic::Ordering::Acquire),
18028            1,
18029            "Should always resolve once after multiple selections and applying the completion"
18030        );
18031        assert_eq!(
18032            editor.text(cx),
18033            "fn main() { let a = ??.other; }",
18034            "Should use resolved data when applying the completion"
18035        );
18036    });
18037}
18038
18039#[gpui::test]
18040async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18041    init_test(cx, |_| {});
18042
18043    let item_0 = lsp::CompletionItem {
18044        label: "abs".into(),
18045        insert_text: Some("abs".into()),
18046        data: Some(json!({ "very": "special"})),
18047        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18048        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18049            lsp::InsertReplaceEdit {
18050                new_text: "abs".to_string(),
18051                insert: lsp::Range::default(),
18052                replace: lsp::Range::default(),
18053            },
18054        )),
18055        ..lsp::CompletionItem::default()
18056    };
18057    let items = iter::once(item_0.clone())
18058        .chain((11..51).map(|i| lsp::CompletionItem {
18059            label: format!("item_{}", i),
18060            insert_text: Some(format!("item_{}", i)),
18061            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18062            ..lsp::CompletionItem::default()
18063        }))
18064        .collect::<Vec<_>>();
18065
18066    let default_commit_characters = vec!["?".to_string()];
18067    let default_data = json!({ "default": "data"});
18068    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18069    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18070    let default_edit_range = lsp::Range {
18071        start: lsp::Position {
18072            line: 0,
18073            character: 5,
18074        },
18075        end: lsp::Position {
18076            line: 0,
18077            character: 5,
18078        },
18079    };
18080
18081    let mut cx = EditorLspTestContext::new_rust(
18082        lsp::ServerCapabilities {
18083            completion_provider: Some(lsp::CompletionOptions {
18084                trigger_characters: Some(vec![".".to_string()]),
18085                resolve_provider: Some(true),
18086                ..Default::default()
18087            }),
18088            ..Default::default()
18089        },
18090        cx,
18091    )
18092    .await;
18093
18094    cx.set_state("fn main() { let a = 2ˇ; }");
18095    cx.simulate_keystroke(".");
18096
18097    let completion_data = default_data.clone();
18098    let completion_characters = default_commit_characters.clone();
18099    let completion_items = items.clone();
18100    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18101        let default_data = completion_data.clone();
18102        let default_commit_characters = completion_characters.clone();
18103        let items = completion_items.clone();
18104        async move {
18105            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18106                items,
18107                item_defaults: Some(lsp::CompletionListItemDefaults {
18108                    data: Some(default_data.clone()),
18109                    commit_characters: Some(default_commit_characters.clone()),
18110                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18111                        default_edit_range,
18112                    )),
18113                    insert_text_format: Some(default_insert_text_format),
18114                    insert_text_mode: Some(default_insert_text_mode),
18115                }),
18116                ..lsp::CompletionList::default()
18117            })))
18118        }
18119    })
18120    .next()
18121    .await;
18122
18123    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18124    cx.lsp
18125        .server
18126        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18127            let closure_resolved_items = resolved_items.clone();
18128            move |item_to_resolve, _| {
18129                let closure_resolved_items = closure_resolved_items.clone();
18130                async move {
18131                    closure_resolved_items.lock().push(item_to_resolve.clone());
18132                    Ok(item_to_resolve)
18133                }
18134            }
18135        })
18136        .detach();
18137
18138    cx.condition(|editor, _| editor.context_menu_visible())
18139        .await;
18140    cx.run_until_parked();
18141    cx.update_editor(|editor, _, _| {
18142        let menu = editor.context_menu.borrow_mut();
18143        match menu.as_ref().expect("should have the completions menu") {
18144            CodeContextMenu::Completions(completions_menu) => {
18145                assert_eq!(
18146                    completions_menu
18147                        .entries
18148                        .borrow()
18149                        .iter()
18150                        .map(|mat| mat.string.clone())
18151                        .collect::<Vec<String>>(),
18152                    items
18153                        .iter()
18154                        .map(|completion| completion.label.clone())
18155                        .collect::<Vec<String>>()
18156                );
18157            }
18158            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18159        }
18160    });
18161    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18162    // with 4 from the end.
18163    assert_eq!(
18164        *resolved_items.lock(),
18165        [&items[0..16], &items[items.len() - 4..items.len()]]
18166            .concat()
18167            .iter()
18168            .cloned()
18169            .map(|mut item| {
18170                if item.data.is_none() {
18171                    item.data = Some(default_data.clone());
18172                }
18173                item
18174            })
18175            .collect::<Vec<lsp::CompletionItem>>(),
18176        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18177    );
18178    resolved_items.lock().clear();
18179
18180    cx.update_editor(|editor, window, cx| {
18181        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18182    });
18183    cx.run_until_parked();
18184    // Completions that have already been resolved are skipped.
18185    assert_eq!(
18186        *resolved_items.lock(),
18187        items[items.len() - 17..items.len() - 4]
18188            .iter()
18189            .cloned()
18190            .map(|mut item| {
18191                if item.data.is_none() {
18192                    item.data = Some(default_data.clone());
18193                }
18194                item
18195            })
18196            .collect::<Vec<lsp::CompletionItem>>()
18197    );
18198    resolved_items.lock().clear();
18199}
18200
18201#[gpui::test]
18202async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18203    init_test(cx, |_| {});
18204
18205    let mut cx = EditorLspTestContext::new(
18206        Language::new(
18207            LanguageConfig {
18208                matcher: LanguageMatcher {
18209                    path_suffixes: vec!["jsx".into()],
18210                    ..Default::default()
18211                },
18212                overrides: [(
18213                    "element".into(),
18214                    LanguageConfigOverride {
18215                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18216                        ..Default::default()
18217                    },
18218                )]
18219                .into_iter()
18220                .collect(),
18221                ..Default::default()
18222            },
18223            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18224        )
18225        .with_override_query("(jsx_self_closing_element) @element")
18226        .unwrap(),
18227        lsp::ServerCapabilities {
18228            completion_provider: Some(lsp::CompletionOptions {
18229                trigger_characters: Some(vec![":".to_string()]),
18230                ..Default::default()
18231            }),
18232            ..Default::default()
18233        },
18234        cx,
18235    )
18236    .await;
18237
18238    cx.lsp
18239        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18240            Ok(Some(lsp::CompletionResponse::Array(vec![
18241                lsp::CompletionItem {
18242                    label: "bg-blue".into(),
18243                    ..Default::default()
18244                },
18245                lsp::CompletionItem {
18246                    label: "bg-red".into(),
18247                    ..Default::default()
18248                },
18249                lsp::CompletionItem {
18250                    label: "bg-yellow".into(),
18251                    ..Default::default()
18252                },
18253            ])))
18254        });
18255
18256    cx.set_state(r#"<p class="bgˇ" />"#);
18257
18258    // Trigger completion when typing a dash, because the dash is an extra
18259    // word character in the 'element' scope, which contains the cursor.
18260    cx.simulate_keystroke("-");
18261    cx.executor().run_until_parked();
18262    cx.update_editor(|editor, _, _| {
18263        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18264        {
18265            assert_eq!(
18266                completion_menu_entries(menu),
18267                &["bg-blue", "bg-red", "bg-yellow"]
18268            );
18269        } else {
18270            panic!("expected completion menu to be open");
18271        }
18272    });
18273
18274    cx.simulate_keystroke("l");
18275    cx.executor().run_until_parked();
18276    cx.update_editor(|editor, _, _| {
18277        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18278        {
18279            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18280        } else {
18281            panic!("expected completion menu to be open");
18282        }
18283    });
18284
18285    // When filtering completions, consider the character after the '-' to
18286    // be the start of a subword.
18287    cx.set_state(r#"<p class="yelˇ" />"#);
18288    cx.simulate_keystroke("l");
18289    cx.executor().run_until_parked();
18290    cx.update_editor(|editor, _, _| {
18291        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18292        {
18293            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18294        } else {
18295            panic!("expected completion menu to be open");
18296        }
18297    });
18298}
18299
18300fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18301    let entries = menu.entries.borrow();
18302    entries.iter().map(|mat| mat.string.clone()).collect()
18303}
18304
18305#[gpui::test]
18306async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18307    init_test(cx, |settings| {
18308        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18309    });
18310
18311    let fs = FakeFs::new(cx.executor());
18312    fs.insert_file(path!("/file.ts"), Default::default()).await;
18313
18314    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18315    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18316
18317    language_registry.add(Arc::new(Language::new(
18318        LanguageConfig {
18319            name: "TypeScript".into(),
18320            matcher: LanguageMatcher {
18321                path_suffixes: vec!["ts".to_string()],
18322                ..Default::default()
18323            },
18324            ..Default::default()
18325        },
18326        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18327    )));
18328    update_test_language_settings(cx, |settings| {
18329        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18330    });
18331
18332    let test_plugin = "test_plugin";
18333    let _ = language_registry.register_fake_lsp(
18334        "TypeScript",
18335        FakeLspAdapter {
18336            prettier_plugins: vec![test_plugin],
18337            ..Default::default()
18338        },
18339    );
18340
18341    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18342    let buffer = project
18343        .update(cx, |project, cx| {
18344            project.open_local_buffer(path!("/file.ts"), cx)
18345        })
18346        .await
18347        .unwrap();
18348
18349    let buffer_text = "one\ntwo\nthree\n";
18350    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18351    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18352    editor.update_in(cx, |editor, window, cx| {
18353        editor.set_text(buffer_text, window, cx)
18354    });
18355
18356    editor
18357        .update_in(cx, |editor, window, cx| {
18358            editor.perform_format(
18359                project.clone(),
18360                FormatTrigger::Manual,
18361                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18362                window,
18363                cx,
18364            )
18365        })
18366        .unwrap()
18367        .await;
18368    assert_eq!(
18369        editor.update(cx, |editor, cx| editor.text(cx)),
18370        buffer_text.to_string() + prettier_format_suffix,
18371        "Test prettier formatting was not applied to the original buffer text",
18372    );
18373
18374    update_test_language_settings(cx, |settings| {
18375        settings.defaults.formatter = Some(FormatterList::default())
18376    });
18377    let format = editor.update_in(cx, |editor, window, cx| {
18378        editor.perform_format(
18379            project.clone(),
18380            FormatTrigger::Manual,
18381            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18382            window,
18383            cx,
18384        )
18385    });
18386    format.await.unwrap();
18387    assert_eq!(
18388        editor.update(cx, |editor, cx| editor.text(cx)),
18389        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18390        "Autoformatting (via test prettier) was not applied to the original buffer text",
18391    );
18392}
18393
18394#[gpui::test]
18395async fn test_addition_reverts(cx: &mut TestAppContext) {
18396    init_test(cx, |_| {});
18397    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18398    let base_text = indoc! {r#"
18399        struct Row;
18400        struct Row1;
18401        struct Row2;
18402
18403        struct Row4;
18404        struct Row5;
18405        struct Row6;
18406
18407        struct Row8;
18408        struct Row9;
18409        struct Row10;"#};
18410
18411    // When addition hunks are not adjacent to carets, no hunk revert is performed
18412    assert_hunk_revert(
18413        indoc! {r#"struct Row;
18414                   struct Row1;
18415                   struct Row1.1;
18416                   struct Row1.2;
18417                   struct Row2;ˇ
18418
18419                   struct Row4;
18420                   struct Row5;
18421                   struct Row6;
18422
18423                   struct Row8;
18424                   ˇstruct Row9;
18425                   struct Row9.1;
18426                   struct Row9.2;
18427                   struct Row9.3;
18428                   struct Row10;"#},
18429        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18430        indoc! {r#"struct Row;
18431                   struct Row1;
18432                   struct Row1.1;
18433                   struct Row1.2;
18434                   struct Row2;ˇ
18435
18436                   struct Row4;
18437                   struct Row5;
18438                   struct Row6;
18439
18440                   struct Row8;
18441                   ˇstruct Row9;
18442                   struct Row9.1;
18443                   struct Row9.2;
18444                   struct Row9.3;
18445                   struct Row10;"#},
18446        base_text,
18447        &mut cx,
18448    );
18449    // Same for selections
18450    assert_hunk_revert(
18451        indoc! {r#"struct Row;
18452                   struct Row1;
18453                   struct Row2;
18454                   struct Row2.1;
18455                   struct Row2.2;
18456                   «ˇ
18457                   struct Row4;
18458                   struct» Row5;
18459                   «struct Row6;
18460                   ˇ»
18461                   struct Row9.1;
18462                   struct Row9.2;
18463                   struct Row9.3;
18464                   struct Row8;
18465                   struct Row9;
18466                   struct Row10;"#},
18467        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18468        indoc! {r#"struct Row;
18469                   struct Row1;
18470                   struct Row2;
18471                   struct Row2.1;
18472                   struct Row2.2;
18473                   «ˇ
18474                   struct Row4;
18475                   struct» Row5;
18476                   «struct Row6;
18477                   ˇ»
18478                   struct Row9.1;
18479                   struct Row9.2;
18480                   struct Row9.3;
18481                   struct Row8;
18482                   struct Row9;
18483                   struct Row10;"#},
18484        base_text,
18485        &mut cx,
18486    );
18487
18488    // When carets and selections intersect the addition hunks, those are reverted.
18489    // Adjacent carets got merged.
18490    assert_hunk_revert(
18491        indoc! {r#"struct Row;
18492                   ˇ// something on the top
18493                   struct Row1;
18494                   struct Row2;
18495                   struct Roˇw3.1;
18496                   struct Row2.2;
18497                   struct Row2.3;ˇ
18498
18499                   struct Row4;
18500                   struct ˇRow5.1;
18501                   struct Row5.2;
18502                   struct «Rowˇ»5.3;
18503                   struct Row5;
18504                   struct Row6;
18505                   ˇ
18506                   struct Row9.1;
18507                   struct «Rowˇ»9.2;
18508                   struct «ˇRow»9.3;
18509                   struct Row8;
18510                   struct Row9;
18511                   «ˇ// something on bottom»
18512                   struct Row10;"#},
18513        vec![
18514            DiffHunkStatusKind::Added,
18515            DiffHunkStatusKind::Added,
18516            DiffHunkStatusKind::Added,
18517            DiffHunkStatusKind::Added,
18518            DiffHunkStatusKind::Added,
18519        ],
18520        indoc! {r#"struct Row;
18521                   ˇstruct Row1;
18522                   struct Row2;
18523                   ˇ
18524                   struct Row4;
18525                   ˇstruct Row5;
18526                   struct Row6;
18527                   ˇ
18528                   ˇstruct Row8;
18529                   struct Row9;
18530                   ˇstruct Row10;"#},
18531        base_text,
18532        &mut cx,
18533    );
18534}
18535
18536#[gpui::test]
18537async fn test_modification_reverts(cx: &mut TestAppContext) {
18538    init_test(cx, |_| {});
18539    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18540    let base_text = indoc! {r#"
18541        struct Row;
18542        struct Row1;
18543        struct Row2;
18544
18545        struct Row4;
18546        struct Row5;
18547        struct Row6;
18548
18549        struct Row8;
18550        struct Row9;
18551        struct Row10;"#};
18552
18553    // Modification hunks behave the same as the addition ones.
18554    assert_hunk_revert(
18555        indoc! {r#"struct Row;
18556                   struct Row1;
18557                   struct Row33;
18558                   ˇ
18559                   struct Row4;
18560                   struct Row5;
18561                   struct Row6;
18562                   ˇ
18563                   struct Row99;
18564                   struct Row9;
18565                   struct Row10;"#},
18566        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18567        indoc! {r#"struct Row;
18568                   struct Row1;
18569                   struct Row33;
18570                   ˇ
18571                   struct Row4;
18572                   struct Row5;
18573                   struct Row6;
18574                   ˇ
18575                   struct Row99;
18576                   struct Row9;
18577                   struct Row10;"#},
18578        base_text,
18579        &mut cx,
18580    );
18581    assert_hunk_revert(
18582        indoc! {r#"struct Row;
18583                   struct Row1;
18584                   struct Row33;
18585                   «ˇ
18586                   struct Row4;
18587                   struct» Row5;
18588                   «struct Row6;
18589                   ˇ»
18590                   struct Row99;
18591                   struct Row9;
18592                   struct Row10;"#},
18593        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18594        indoc! {r#"struct Row;
18595                   struct Row1;
18596                   struct Row33;
18597                   «ˇ
18598                   struct Row4;
18599                   struct» Row5;
18600                   «struct Row6;
18601                   ˇ»
18602                   struct Row99;
18603                   struct Row9;
18604                   struct Row10;"#},
18605        base_text,
18606        &mut cx,
18607    );
18608
18609    assert_hunk_revert(
18610        indoc! {r#"ˇstruct Row1.1;
18611                   struct Row1;
18612                   «ˇstr»uct Row22;
18613
18614                   struct ˇRow44;
18615                   struct Row5;
18616                   struct «Rˇ»ow66;ˇ
18617
18618                   «struˇ»ct Row88;
18619                   struct Row9;
18620                   struct Row1011;ˇ"#},
18621        vec![
18622            DiffHunkStatusKind::Modified,
18623            DiffHunkStatusKind::Modified,
18624            DiffHunkStatusKind::Modified,
18625            DiffHunkStatusKind::Modified,
18626            DiffHunkStatusKind::Modified,
18627            DiffHunkStatusKind::Modified,
18628        ],
18629        indoc! {r#"struct Row;
18630                   ˇstruct Row1;
18631                   struct Row2;
18632                   ˇ
18633                   struct Row4;
18634                   ˇstruct Row5;
18635                   struct Row6;
18636                   ˇ
18637                   struct Row8;
18638                   ˇstruct Row9;
18639                   struct Row10;ˇ"#},
18640        base_text,
18641        &mut cx,
18642    );
18643}
18644
18645#[gpui::test]
18646async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18647    init_test(cx, |_| {});
18648    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18649    let base_text = indoc! {r#"
18650        one
18651
18652        two
18653        three
18654        "#};
18655
18656    cx.set_head_text(base_text);
18657    cx.set_state("\nˇ\n");
18658    cx.executor().run_until_parked();
18659    cx.update_editor(|editor, _window, cx| {
18660        editor.expand_selected_diff_hunks(cx);
18661    });
18662    cx.executor().run_until_parked();
18663    cx.update_editor(|editor, window, cx| {
18664        editor.backspace(&Default::default(), window, cx);
18665    });
18666    cx.run_until_parked();
18667    cx.assert_state_with_diff(
18668        indoc! {r#"
18669
18670        - two
18671        - threeˇ
18672        +
18673        "#}
18674        .to_string(),
18675    );
18676}
18677
18678#[gpui::test]
18679async fn test_deletion_reverts(cx: &mut TestAppContext) {
18680    init_test(cx, |_| {});
18681    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18682    let base_text = indoc! {r#"struct Row;
18683struct Row1;
18684struct Row2;
18685
18686struct Row4;
18687struct Row5;
18688struct Row6;
18689
18690struct Row8;
18691struct Row9;
18692struct Row10;"#};
18693
18694    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18695    assert_hunk_revert(
18696        indoc! {r#"struct Row;
18697                   struct Row2;
18698
18699                   ˇstruct Row4;
18700                   struct Row5;
18701                   struct Row6;
18702                   ˇ
18703                   struct Row8;
18704                   struct Row10;"#},
18705        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18706        indoc! {r#"struct Row;
18707                   struct Row2;
18708
18709                   ˇstruct Row4;
18710                   struct Row5;
18711                   struct Row6;
18712                   ˇ
18713                   struct Row8;
18714                   struct Row10;"#},
18715        base_text,
18716        &mut cx,
18717    );
18718    assert_hunk_revert(
18719        indoc! {r#"struct Row;
18720                   struct Row2;
18721
18722                   «ˇstruct Row4;
18723                   struct» Row5;
18724                   «struct Row6;
18725                   ˇ»
18726                   struct Row8;
18727                   struct Row10;"#},
18728        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18729        indoc! {r#"struct Row;
18730                   struct Row2;
18731
18732                   «ˇstruct Row4;
18733                   struct» Row5;
18734                   «struct Row6;
18735                   ˇ»
18736                   struct Row8;
18737                   struct Row10;"#},
18738        base_text,
18739        &mut cx,
18740    );
18741
18742    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18743    assert_hunk_revert(
18744        indoc! {r#"struct Row;
18745                   ˇstruct Row2;
18746
18747                   struct Row4;
18748                   struct Row5;
18749                   struct Row6;
18750
18751                   struct Row8;ˇ
18752                   struct Row10;"#},
18753        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18754        indoc! {r#"struct Row;
18755                   struct Row1;
18756                   ˇstruct Row2;
18757
18758                   struct Row4;
18759                   struct Row5;
18760                   struct Row6;
18761
18762                   struct Row8;ˇ
18763                   struct Row9;
18764                   struct Row10;"#},
18765        base_text,
18766        &mut cx,
18767    );
18768    assert_hunk_revert(
18769        indoc! {r#"struct Row;
18770                   struct Row2«ˇ;
18771                   struct Row4;
18772                   struct» Row5;
18773                   «struct Row6;
18774
18775                   struct Row8;ˇ»
18776                   struct Row10;"#},
18777        vec![
18778            DiffHunkStatusKind::Deleted,
18779            DiffHunkStatusKind::Deleted,
18780            DiffHunkStatusKind::Deleted,
18781        ],
18782        indoc! {r#"struct Row;
18783                   struct Row1;
18784                   struct Row2«ˇ;
18785
18786                   struct Row4;
18787                   struct» Row5;
18788                   «struct Row6;
18789
18790                   struct Row8;ˇ»
18791                   struct Row9;
18792                   struct Row10;"#},
18793        base_text,
18794        &mut cx,
18795    );
18796}
18797
18798#[gpui::test]
18799async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18800    init_test(cx, |_| {});
18801
18802    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18803    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18804    let base_text_3 =
18805        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18806
18807    let text_1 = edit_first_char_of_every_line(base_text_1);
18808    let text_2 = edit_first_char_of_every_line(base_text_2);
18809    let text_3 = edit_first_char_of_every_line(base_text_3);
18810
18811    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18812    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18813    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18814
18815    let multibuffer = cx.new(|cx| {
18816        let mut multibuffer = MultiBuffer::new(ReadWrite);
18817        multibuffer.push_excerpts(
18818            buffer_1.clone(),
18819            [
18820                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18821                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18822                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18823            ],
18824            cx,
18825        );
18826        multibuffer.push_excerpts(
18827            buffer_2.clone(),
18828            [
18829                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18830                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18831                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18832            ],
18833            cx,
18834        );
18835        multibuffer.push_excerpts(
18836            buffer_3.clone(),
18837            [
18838                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18839                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18840                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18841            ],
18842            cx,
18843        );
18844        multibuffer
18845    });
18846
18847    let fs = FakeFs::new(cx.executor());
18848    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18849    let (editor, cx) = cx
18850        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18851    editor.update_in(cx, |editor, _window, cx| {
18852        for (buffer, diff_base) in [
18853            (buffer_1.clone(), base_text_1),
18854            (buffer_2.clone(), base_text_2),
18855            (buffer_3.clone(), base_text_3),
18856        ] {
18857            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18858            editor
18859                .buffer
18860                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18861        }
18862    });
18863    cx.executor().run_until_parked();
18864
18865    editor.update_in(cx, |editor, window, cx| {
18866        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}");
18867        editor.select_all(&SelectAll, window, cx);
18868        editor.git_restore(&Default::default(), window, cx);
18869    });
18870    cx.executor().run_until_parked();
18871
18872    // When all ranges are selected, all buffer hunks are reverted.
18873    editor.update(cx, |editor, cx| {
18874        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");
18875    });
18876    buffer_1.update(cx, |buffer, _| {
18877        assert_eq!(buffer.text(), base_text_1);
18878    });
18879    buffer_2.update(cx, |buffer, _| {
18880        assert_eq!(buffer.text(), base_text_2);
18881    });
18882    buffer_3.update(cx, |buffer, _| {
18883        assert_eq!(buffer.text(), base_text_3);
18884    });
18885
18886    editor.update_in(cx, |editor, window, cx| {
18887        editor.undo(&Default::default(), window, cx);
18888    });
18889
18890    editor.update_in(cx, |editor, window, cx| {
18891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18892            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18893        });
18894        editor.git_restore(&Default::default(), window, cx);
18895    });
18896
18897    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18898    // but not affect buffer_2 and its related excerpts.
18899    editor.update(cx, |editor, cx| {
18900        assert_eq!(
18901            editor.text(cx),
18902            "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}"
18903        );
18904    });
18905    buffer_1.update(cx, |buffer, _| {
18906        assert_eq!(buffer.text(), base_text_1);
18907    });
18908    buffer_2.update(cx, |buffer, _| {
18909        assert_eq!(
18910            buffer.text(),
18911            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18912        );
18913    });
18914    buffer_3.update(cx, |buffer, _| {
18915        assert_eq!(
18916            buffer.text(),
18917            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18918        );
18919    });
18920
18921    fn edit_first_char_of_every_line(text: &str) -> String {
18922        text.split('\n')
18923            .map(|line| format!("X{}", &line[1..]))
18924            .collect::<Vec<_>>()
18925            .join("\n")
18926    }
18927}
18928
18929#[gpui::test]
18930async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18931    init_test(cx, |_| {});
18932
18933    let cols = 4;
18934    let rows = 10;
18935    let sample_text_1 = sample_text(rows, cols, 'a');
18936    assert_eq!(
18937        sample_text_1,
18938        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18939    );
18940    let sample_text_2 = sample_text(rows, cols, 'l');
18941    assert_eq!(
18942        sample_text_2,
18943        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18944    );
18945    let sample_text_3 = sample_text(rows, cols, 'v');
18946    assert_eq!(
18947        sample_text_3,
18948        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18949    );
18950
18951    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18952    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18953    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18954
18955    let multi_buffer = cx.new(|cx| {
18956        let mut multibuffer = MultiBuffer::new(ReadWrite);
18957        multibuffer.push_excerpts(
18958            buffer_1.clone(),
18959            [
18960                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18961                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18962                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18963            ],
18964            cx,
18965        );
18966        multibuffer.push_excerpts(
18967            buffer_2.clone(),
18968            [
18969                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18970                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18971                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18972            ],
18973            cx,
18974        );
18975        multibuffer.push_excerpts(
18976            buffer_3.clone(),
18977            [
18978                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18979                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18980                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18981            ],
18982            cx,
18983        );
18984        multibuffer
18985    });
18986
18987    let fs = FakeFs::new(cx.executor());
18988    fs.insert_tree(
18989        "/a",
18990        json!({
18991            "main.rs": sample_text_1,
18992            "other.rs": sample_text_2,
18993            "lib.rs": sample_text_3,
18994        }),
18995    )
18996    .await;
18997    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18998    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18999    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19000    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19001        Editor::new(
19002            EditorMode::full(),
19003            multi_buffer,
19004            Some(project.clone()),
19005            window,
19006            cx,
19007        )
19008    });
19009    let multibuffer_item_id = workspace
19010        .update(cx, |workspace, window, cx| {
19011            assert!(
19012                workspace.active_item(cx).is_none(),
19013                "active item should be None before the first item is added"
19014            );
19015            workspace.add_item_to_active_pane(
19016                Box::new(multi_buffer_editor.clone()),
19017                None,
19018                true,
19019                window,
19020                cx,
19021            );
19022            let active_item = workspace
19023                .active_item(cx)
19024                .expect("should have an active item after adding the multi buffer");
19025            assert_eq!(
19026                active_item.buffer_kind(cx),
19027                ItemBufferKind::Multibuffer,
19028                "A multi buffer was expected to active after adding"
19029            );
19030            active_item.item_id()
19031        })
19032        .unwrap();
19033    cx.executor().run_until_parked();
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(1..2)),
19041        );
19042        editor.open_excerpts(&OpenExcerpts, window, cx);
19043    });
19044    cx.executor().run_until_parked();
19045    let first_item_id = 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 1st buffer");
19050            let first_item_id = active_item.item_id();
19051            assert_ne!(
19052                first_item_id, multibuffer_item_id,
19053                "Should navigate into the 1st buffer and activate it"
19054            );
19055            assert_eq!(
19056                active_item.buffer_kind(cx),
19057                ItemBufferKind::Singleton,
19058                "New active item should be a singleton buffer"
19059            );
19060            assert_eq!(
19061                active_item
19062                    .act_as::<Editor>(cx)
19063                    .expect("should have navigated into an editor for the 1st buffer")
19064                    .read(cx)
19065                    .text(cx),
19066                sample_text_1
19067            );
19068
19069            workspace
19070                .go_back(workspace.active_pane().downgrade(), window, cx)
19071                .detach_and_log_err(cx);
19072
19073            first_item_id
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");
19082            assert_eq!(
19083                active_item.item_id(),
19084                multibuffer_item_id,
19085                "Should navigate back to the multi buffer"
19086            );
19087            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19088        })
19089        .unwrap();
19090
19091    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19092        editor.change_selections(
19093            SelectionEffects::scroll(Autoscroll::Next),
19094            window,
19095            cx,
19096            |s| s.select_ranges(Some(39..40)),
19097        );
19098        editor.open_excerpts(&OpenExcerpts, window, cx);
19099    });
19100    cx.executor().run_until_parked();
19101    let second_item_id = workspace
19102        .update(cx, |workspace, window, cx| {
19103            let active_item = workspace
19104                .active_item(cx)
19105                .expect("should have an active item after navigating into the 2nd buffer");
19106            let second_item_id = active_item.item_id();
19107            assert_ne!(
19108                second_item_id, multibuffer_item_id,
19109                "Should navigate away from the multibuffer"
19110            );
19111            assert_ne!(
19112                second_item_id, first_item_id,
19113                "Should navigate into the 2nd buffer and activate it"
19114            );
19115            assert_eq!(
19116                active_item.buffer_kind(cx),
19117                ItemBufferKind::Singleton,
19118                "New active item should be a singleton buffer"
19119            );
19120            assert_eq!(
19121                active_item
19122                    .act_as::<Editor>(cx)
19123                    .expect("should have navigated into an editor")
19124                    .read(cx)
19125                    .text(cx),
19126                sample_text_2
19127            );
19128
19129            workspace
19130                .go_back(workspace.active_pane().downgrade(), window, cx)
19131                .detach_and_log_err(cx);
19132
19133            second_item_id
19134        })
19135        .unwrap();
19136    cx.executor().run_until_parked();
19137    workspace
19138        .update(cx, |workspace, _, cx| {
19139            let active_item = workspace
19140                .active_item(cx)
19141                .expect("should have an active item after navigating back from the 2nd buffer");
19142            assert_eq!(
19143                active_item.item_id(),
19144                multibuffer_item_id,
19145                "Should navigate back from the 2nd buffer to the multi buffer"
19146            );
19147            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19148        })
19149        .unwrap();
19150
19151    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19152        editor.change_selections(
19153            SelectionEffects::scroll(Autoscroll::Next),
19154            window,
19155            cx,
19156            |s| s.select_ranges(Some(70..70)),
19157        );
19158        editor.open_excerpts(&OpenExcerpts, window, cx);
19159    });
19160    cx.executor().run_until_parked();
19161    workspace
19162        .update(cx, |workspace, window, cx| {
19163            let active_item = workspace
19164                .active_item(cx)
19165                .expect("should have an active item after navigating into the 3rd buffer");
19166            let third_item_id = active_item.item_id();
19167            assert_ne!(
19168                third_item_id, multibuffer_item_id,
19169                "Should navigate into the 3rd buffer and activate it"
19170            );
19171            assert_ne!(third_item_id, first_item_id);
19172            assert_ne!(third_item_id, second_item_id);
19173            assert_eq!(
19174                active_item.buffer_kind(cx),
19175                ItemBufferKind::Singleton,
19176                "New active item should be a singleton buffer"
19177            );
19178            assert_eq!(
19179                active_item
19180                    .act_as::<Editor>(cx)
19181                    .expect("should have navigated into an editor")
19182                    .read(cx)
19183                    .text(cx),
19184                sample_text_3
19185            );
19186
19187            workspace
19188                .go_back(workspace.active_pane().downgrade(), window, cx)
19189                .detach_and_log_err(cx);
19190        })
19191        .unwrap();
19192    cx.executor().run_until_parked();
19193    workspace
19194        .update(cx, |workspace, _, cx| {
19195            let active_item = workspace
19196                .active_item(cx)
19197                .expect("should have an active item after navigating back from the 3rd buffer");
19198            assert_eq!(
19199                active_item.item_id(),
19200                multibuffer_item_id,
19201                "Should navigate back from the 3rd buffer to the multi buffer"
19202            );
19203            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19204        })
19205        .unwrap();
19206}
19207
19208#[gpui::test]
19209async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19210    init_test(cx, |_| {});
19211
19212    let mut cx = EditorTestContext::new(cx).await;
19213
19214    let diff_base = r#"
19215        use some::mod;
19216
19217        const A: u32 = 42;
19218
19219        fn main() {
19220            println!("hello");
19221
19222            println!("world");
19223        }
19224        "#
19225    .unindent();
19226
19227    cx.set_state(
19228        &r#"
19229        use some::modified;
19230
19231        ˇ
19232        fn main() {
19233            println!("hello there");
19234
19235            println!("around the");
19236            println!("world");
19237        }
19238        "#
19239        .unindent(),
19240    );
19241
19242    cx.set_head_text(&diff_base);
19243    executor.run_until_parked();
19244
19245    cx.update_editor(|editor, window, cx| {
19246        editor.go_to_next_hunk(&GoToHunk, window, cx);
19247        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19248    });
19249    executor.run_until_parked();
19250    cx.assert_state_with_diff(
19251        r#"
19252          use some::modified;
19253
19254
19255          fn main() {
19256        -     println!("hello");
19257        + ˇ    println!("hello there");
19258
19259              println!("around the");
19260              println!("world");
19261          }
19262        "#
19263        .unindent(),
19264    );
19265
19266    cx.update_editor(|editor, window, cx| {
19267        for _ in 0..2 {
19268            editor.go_to_next_hunk(&GoToHunk, window, cx);
19269            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19270        }
19271    });
19272    executor.run_until_parked();
19273    cx.assert_state_with_diff(
19274        r#"
19275        - use some::mod;
19276        + ˇuse some::modified;
19277
19278
19279          fn main() {
19280        -     println!("hello");
19281        +     println!("hello there");
19282
19283        +     println!("around the");
19284              println!("world");
19285          }
19286        "#
19287        .unindent(),
19288    );
19289
19290    cx.update_editor(|editor, window, cx| {
19291        editor.go_to_next_hunk(&GoToHunk, window, cx);
19292        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19293    });
19294    executor.run_until_parked();
19295    cx.assert_state_with_diff(
19296        r#"
19297        - use some::mod;
19298        + use some::modified;
19299
19300        - const A: u32 = 42;
19301          ˇ
19302          fn main() {
19303        -     println!("hello");
19304        +     println!("hello there");
19305
19306        +     println!("around the");
19307              println!("world");
19308          }
19309        "#
19310        .unindent(),
19311    );
19312
19313    cx.update_editor(|editor, window, cx| {
19314        editor.cancel(&Cancel, window, cx);
19315    });
19316
19317    cx.assert_state_with_diff(
19318        r#"
19319          use some::modified;
19320
19321          ˇ
19322          fn main() {
19323              println!("hello there");
19324
19325              println!("around the");
19326              println!("world");
19327          }
19328        "#
19329        .unindent(),
19330    );
19331}
19332
19333#[gpui::test]
19334async fn test_diff_base_change_with_expanded_diff_hunks(
19335    executor: BackgroundExecutor,
19336    cx: &mut TestAppContext,
19337) {
19338    init_test(cx, |_| {});
19339
19340    let mut cx = EditorTestContext::new(cx).await;
19341
19342    let diff_base = r#"
19343        use some::mod1;
19344        use some::mod2;
19345
19346        const A: u32 = 42;
19347        const B: u32 = 42;
19348        const C: u32 = 42;
19349
19350        fn main() {
19351            println!("hello");
19352
19353            println!("world");
19354        }
19355        "#
19356    .unindent();
19357
19358    cx.set_state(
19359        &r#"
19360        use some::mod2;
19361
19362        const A: u32 = 42;
19363        const C: u32 = 42;
19364
19365        fn main(ˇ) {
19366            //println!("hello");
19367
19368            println!("world");
19369            //
19370            //
19371        }
19372        "#
19373        .unindent(),
19374    );
19375
19376    cx.set_head_text(&diff_base);
19377    executor.run_until_parked();
19378
19379    cx.update_editor(|editor, window, cx| {
19380        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19381    });
19382    executor.run_until_parked();
19383    cx.assert_state_with_diff(
19384        r#"
19385        - use some::mod1;
19386          use some::mod2;
19387
19388          const A: u32 = 42;
19389        - const B: u32 = 42;
19390          const C: u32 = 42;
19391
19392          fn main(ˇ) {
19393        -     println!("hello");
19394        +     //println!("hello");
19395
19396              println!("world");
19397        +     //
19398        +     //
19399          }
19400        "#
19401        .unindent(),
19402    );
19403
19404    cx.set_head_text("new diff base!");
19405    executor.run_until_parked();
19406    cx.assert_state_with_diff(
19407        r#"
19408        - new diff base!
19409        + use some::mod2;
19410        +
19411        + const A: u32 = 42;
19412        + const C: u32 = 42;
19413        +
19414        + fn main(ˇ) {
19415        +     //println!("hello");
19416        +
19417        +     println!("world");
19418        +     //
19419        +     //
19420        + }
19421        "#
19422        .unindent(),
19423    );
19424}
19425
19426#[gpui::test]
19427async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19428    init_test(cx, |_| {});
19429
19430    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19431    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19432    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19433    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19434    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19435    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19436
19437    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19438    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19439    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19440
19441    let multi_buffer = cx.new(|cx| {
19442        let mut multibuffer = MultiBuffer::new(ReadWrite);
19443        multibuffer.push_excerpts(
19444            buffer_1.clone(),
19445            [
19446                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19447                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19448                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19449            ],
19450            cx,
19451        );
19452        multibuffer.push_excerpts(
19453            buffer_2.clone(),
19454            [
19455                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19456                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19457                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19458            ],
19459            cx,
19460        );
19461        multibuffer.push_excerpts(
19462            buffer_3.clone(),
19463            [
19464                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19465                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19466                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19467            ],
19468            cx,
19469        );
19470        multibuffer
19471    });
19472
19473    let editor =
19474        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19475    editor
19476        .update(cx, |editor, _window, cx| {
19477            for (buffer, diff_base) in [
19478                (buffer_1.clone(), file_1_old),
19479                (buffer_2.clone(), file_2_old),
19480                (buffer_3.clone(), file_3_old),
19481            ] {
19482                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19483                editor
19484                    .buffer
19485                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19486            }
19487        })
19488        .unwrap();
19489
19490    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19491    cx.run_until_parked();
19492
19493    cx.assert_editor_state(
19494        &"
19495            ˇaaa
19496            ccc
19497            ddd
19498
19499            ggg
19500            hhh
19501
19502
19503            lll
19504            mmm
19505            NNN
19506
19507            qqq
19508            rrr
19509
19510            uuu
19511            111
19512            222
19513            333
19514
19515            666
19516            777
19517
19518            000
19519            !!!"
19520        .unindent(),
19521    );
19522
19523    cx.update_editor(|editor, window, cx| {
19524        editor.select_all(&SelectAll, window, cx);
19525        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19526    });
19527    cx.executor().run_until_parked();
19528
19529    cx.assert_state_with_diff(
19530        "
19531            «aaa
19532          - bbb
19533            ccc
19534            ddd
19535
19536            ggg
19537            hhh
19538
19539
19540            lll
19541            mmm
19542          - nnn
19543          + NNN
19544
19545            qqq
19546            rrr
19547
19548            uuu
19549            111
19550            222
19551            333
19552
19553          + 666
19554            777
19555
19556            000
19557            !!!ˇ»"
19558            .unindent(),
19559    );
19560}
19561
19562#[gpui::test]
19563async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19564    init_test(cx, |_| {});
19565
19566    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19567    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19568
19569    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19570    let multi_buffer = cx.new(|cx| {
19571        let mut multibuffer = MultiBuffer::new(ReadWrite);
19572        multibuffer.push_excerpts(
19573            buffer.clone(),
19574            [
19575                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19576                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19577                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19578            ],
19579            cx,
19580        );
19581        multibuffer
19582    });
19583
19584    let editor =
19585        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19586    editor
19587        .update(cx, |editor, _window, cx| {
19588            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19589            editor
19590                .buffer
19591                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19592        })
19593        .unwrap();
19594
19595    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19596    cx.run_until_parked();
19597
19598    cx.update_editor(|editor, window, cx| {
19599        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19600    });
19601    cx.executor().run_until_parked();
19602
19603    // When the start of a hunk coincides with the start of its excerpt,
19604    // the hunk is expanded. When the start of a hunk is earlier than
19605    // the start of its excerpt, the hunk is not expanded.
19606    cx.assert_state_with_diff(
19607        "
19608            ˇaaa
19609          - bbb
19610          + BBB
19611
19612          - ddd
19613          - eee
19614          + DDD
19615          + EEE
19616            fff
19617
19618            iii
19619        "
19620        .unindent(),
19621    );
19622}
19623
19624#[gpui::test]
19625async fn test_edits_around_expanded_insertion_hunks(
19626    executor: BackgroundExecutor,
19627    cx: &mut TestAppContext,
19628) {
19629    init_test(cx, |_| {});
19630
19631    let mut cx = EditorTestContext::new(cx).await;
19632
19633    let diff_base = r#"
19634        use some::mod1;
19635        use some::mod2;
19636
19637        const A: u32 = 42;
19638
19639        fn main() {
19640            println!("hello");
19641
19642            println!("world");
19643        }
19644        "#
19645    .unindent();
19646    executor.run_until_parked();
19647    cx.set_state(
19648        &r#"
19649        use some::mod1;
19650        use some::mod2;
19651
19652        const A: u32 = 42;
19653        const B: u32 = 42;
19654        const C: u32 = 42;
19655        ˇ
19656
19657        fn main() {
19658            println!("hello");
19659
19660            println!("world");
19661        }
19662        "#
19663        .unindent(),
19664    );
19665
19666    cx.set_head_text(&diff_base);
19667    executor.run_until_parked();
19668
19669    cx.update_editor(|editor, window, cx| {
19670        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19671    });
19672    executor.run_until_parked();
19673
19674    cx.assert_state_with_diff(
19675        r#"
19676        use some::mod1;
19677        use some::mod2;
19678
19679        const A: u32 = 42;
19680      + const B: u32 = 42;
19681      + const C: u32 = 42;
19682      + ˇ
19683
19684        fn main() {
19685            println!("hello");
19686
19687            println!("world");
19688        }
19689      "#
19690        .unindent(),
19691    );
19692
19693    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19694    executor.run_until_parked();
19695
19696    cx.assert_state_with_diff(
19697        r#"
19698        use some::mod1;
19699        use some::mod2;
19700
19701        const A: u32 = 42;
19702      + const B: u32 = 42;
19703      + const C: u32 = 42;
19704      + const D: u32 = 42;
19705      + ˇ
19706
19707        fn main() {
19708            println!("hello");
19709
19710            println!("world");
19711        }
19712      "#
19713        .unindent(),
19714    );
19715
19716    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19717    executor.run_until_parked();
19718
19719    cx.assert_state_with_diff(
19720        r#"
19721        use some::mod1;
19722        use some::mod2;
19723
19724        const A: u32 = 42;
19725      + const B: u32 = 42;
19726      + const C: u32 = 42;
19727      + const D: u32 = 42;
19728      + const E: u32 = 42;
19729      + ˇ
19730
19731        fn main() {
19732            println!("hello");
19733
19734            println!("world");
19735        }
19736      "#
19737        .unindent(),
19738    );
19739
19740    cx.update_editor(|editor, window, cx| {
19741        editor.delete_line(&DeleteLine, window, cx);
19742    });
19743    executor.run_until_parked();
19744
19745    cx.assert_state_with_diff(
19746        r#"
19747        use some::mod1;
19748        use some::mod2;
19749
19750        const A: u32 = 42;
19751      + const B: u32 = 42;
19752      + const C: u32 = 42;
19753      + const D: u32 = 42;
19754      + const E: u32 = 42;
19755        ˇ
19756        fn main() {
19757            println!("hello");
19758
19759            println!("world");
19760        }
19761      "#
19762        .unindent(),
19763    );
19764
19765    cx.update_editor(|editor, window, cx| {
19766        editor.move_up(&MoveUp, window, cx);
19767        editor.delete_line(&DeleteLine, window, cx);
19768        editor.move_up(&MoveUp, window, cx);
19769        editor.delete_line(&DeleteLine, window, cx);
19770        editor.move_up(&MoveUp, window, cx);
19771        editor.delete_line(&DeleteLine, window, cx);
19772    });
19773    executor.run_until_parked();
19774    cx.assert_state_with_diff(
19775        r#"
19776        use some::mod1;
19777        use some::mod2;
19778
19779        const A: u32 = 42;
19780      + const B: u32 = 42;
19781        ˇ
19782        fn main() {
19783            println!("hello");
19784
19785            println!("world");
19786        }
19787      "#
19788        .unindent(),
19789    );
19790
19791    cx.update_editor(|editor, window, cx| {
19792        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19793        editor.delete_line(&DeleteLine, window, cx);
19794    });
19795    executor.run_until_parked();
19796    cx.assert_state_with_diff(
19797        r#"
19798        ˇ
19799        fn main() {
19800            println!("hello");
19801
19802            println!("world");
19803        }
19804      "#
19805        .unindent(),
19806    );
19807}
19808
19809#[gpui::test]
19810async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19811    init_test(cx, |_| {});
19812
19813    let mut cx = EditorTestContext::new(cx).await;
19814    cx.set_head_text(indoc! { "
19815        one
19816        two
19817        three
19818        four
19819        five
19820        "
19821    });
19822    cx.set_state(indoc! { "
19823        one
19824        ˇthree
19825        five
19826    "});
19827    cx.run_until_parked();
19828    cx.update_editor(|editor, window, cx| {
19829        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19830    });
19831    cx.assert_state_with_diff(
19832        indoc! { "
19833        one
19834      - two
19835        ˇthree
19836      - four
19837        five
19838    "}
19839        .to_string(),
19840    );
19841    cx.update_editor(|editor, window, cx| {
19842        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19843    });
19844
19845    cx.assert_state_with_diff(
19846        indoc! { "
19847        one
19848        ˇthree
19849        five
19850    "}
19851        .to_string(),
19852    );
19853
19854    cx.set_state(indoc! { "
19855        one
19856        ˇTWO
19857        three
19858        four
19859        five
19860    "});
19861    cx.run_until_parked();
19862    cx.update_editor(|editor, window, cx| {
19863        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19864    });
19865
19866    cx.assert_state_with_diff(
19867        indoc! { "
19868            one
19869          - two
19870          + ˇTWO
19871            three
19872            four
19873            five
19874        "}
19875        .to_string(),
19876    );
19877    cx.update_editor(|editor, window, cx| {
19878        editor.move_up(&Default::default(), window, cx);
19879        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19880    });
19881    cx.assert_state_with_diff(
19882        indoc! { "
19883            one
19884            ˇTWO
19885            three
19886            four
19887            five
19888        "}
19889        .to_string(),
19890    );
19891}
19892
19893#[gpui::test]
19894async fn test_edits_around_expanded_deletion_hunks(
19895    executor: BackgroundExecutor,
19896    cx: &mut TestAppContext,
19897) {
19898    init_test(cx, |_| {});
19899
19900    let mut cx = EditorTestContext::new(cx).await;
19901
19902    let diff_base = r#"
19903        use some::mod1;
19904        use some::mod2;
19905
19906        const A: u32 = 42;
19907        const B: u32 = 42;
19908        const C: u32 = 42;
19909
19910
19911        fn main() {
19912            println!("hello");
19913
19914            println!("world");
19915        }
19916    "#
19917    .unindent();
19918    executor.run_until_parked();
19919    cx.set_state(
19920        &r#"
19921        use some::mod1;
19922        use some::mod2;
19923
19924        ˇconst B: u32 = 42;
19925        const C: u32 = 42;
19926
19927
19928        fn main() {
19929            println!("hello");
19930
19931            println!("world");
19932        }
19933        "#
19934        .unindent(),
19935    );
19936
19937    cx.set_head_text(&diff_base);
19938    executor.run_until_parked();
19939
19940    cx.update_editor(|editor, window, cx| {
19941        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19942    });
19943    executor.run_until_parked();
19944
19945    cx.assert_state_with_diff(
19946        r#"
19947        use some::mod1;
19948        use some::mod2;
19949
19950      - const A: u32 = 42;
19951        ˇconst B: u32 = 42;
19952        const C: u32 = 42;
19953
19954
19955        fn main() {
19956            println!("hello");
19957
19958            println!("world");
19959        }
19960      "#
19961        .unindent(),
19962    );
19963
19964    cx.update_editor(|editor, window, cx| {
19965        editor.delete_line(&DeleteLine, window, cx);
19966    });
19967    executor.run_until_parked();
19968    cx.assert_state_with_diff(
19969        r#"
19970        use some::mod1;
19971        use some::mod2;
19972
19973      - const A: u32 = 42;
19974      - const B: u32 = 42;
19975        ˇconst C: u32 = 42;
19976
19977
19978        fn main() {
19979            println!("hello");
19980
19981            println!("world");
19982        }
19983      "#
19984        .unindent(),
19985    );
19986
19987    cx.update_editor(|editor, window, cx| {
19988        editor.delete_line(&DeleteLine, window, cx);
19989    });
19990    executor.run_until_parked();
19991    cx.assert_state_with_diff(
19992        r#"
19993        use some::mod1;
19994        use some::mod2;
19995
19996      - const A: u32 = 42;
19997      - const B: u32 = 42;
19998      - const C: u32 = 42;
19999        ˇ
20000
20001        fn main() {
20002            println!("hello");
20003
20004            println!("world");
20005        }
20006      "#
20007        .unindent(),
20008    );
20009
20010    cx.update_editor(|editor, window, cx| {
20011        editor.handle_input("replacement", window, cx);
20012    });
20013    executor.run_until_parked();
20014    cx.assert_state_with_diff(
20015        r#"
20016        use some::mod1;
20017        use some::mod2;
20018
20019      - const A: u32 = 42;
20020      - const B: u32 = 42;
20021      - const C: u32 = 42;
20022      -
20023      + replacementˇ
20024
20025        fn main() {
20026            println!("hello");
20027
20028            println!("world");
20029        }
20030      "#
20031        .unindent(),
20032    );
20033}
20034
20035#[gpui::test]
20036async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20037    init_test(cx, |_| {});
20038
20039    let mut cx = EditorTestContext::new(cx).await;
20040
20041    let base_text = r#"
20042        one
20043        two
20044        three
20045        four
20046        five
20047    "#
20048    .unindent();
20049    executor.run_until_parked();
20050    cx.set_state(
20051        &r#"
20052        one
20053        two
20054        fˇour
20055        five
20056        "#
20057        .unindent(),
20058    );
20059
20060    cx.set_head_text(&base_text);
20061    executor.run_until_parked();
20062
20063    cx.update_editor(|editor, window, cx| {
20064        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20065    });
20066    executor.run_until_parked();
20067
20068    cx.assert_state_with_diff(
20069        r#"
20070          one
20071          two
20072        - three
20073          fˇour
20074          five
20075        "#
20076        .unindent(),
20077    );
20078
20079    cx.update_editor(|editor, window, cx| {
20080        editor.backspace(&Backspace, window, cx);
20081        editor.backspace(&Backspace, window, cx);
20082    });
20083    executor.run_until_parked();
20084    cx.assert_state_with_diff(
20085        r#"
20086          one
20087          two
20088        - threeˇ
20089        - four
20090        + our
20091          five
20092        "#
20093        .unindent(),
20094    );
20095}
20096
20097#[gpui::test]
20098async fn test_edit_after_expanded_modification_hunk(
20099    executor: BackgroundExecutor,
20100    cx: &mut TestAppContext,
20101) {
20102    init_test(cx, |_| {});
20103
20104    let mut cx = EditorTestContext::new(cx).await;
20105
20106    let diff_base = r#"
20107        use some::mod1;
20108        use some::mod2;
20109
20110        const A: u32 = 42;
20111        const B: u32 = 42;
20112        const C: u32 = 42;
20113        const D: u32 = 42;
20114
20115
20116        fn main() {
20117            println!("hello");
20118
20119            println!("world");
20120        }"#
20121    .unindent();
20122
20123    cx.set_state(
20124        &r#"
20125        use some::mod1;
20126        use some::mod2;
20127
20128        const A: u32 = 42;
20129        const B: u32 = 42;
20130        const C: u32 = 43ˇ
20131        const D: u32 = 42;
20132
20133
20134        fn main() {
20135            println!("hello");
20136
20137            println!("world");
20138        }"#
20139        .unindent(),
20140    );
20141
20142    cx.set_head_text(&diff_base);
20143    executor.run_until_parked();
20144    cx.update_editor(|editor, window, cx| {
20145        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20146    });
20147    executor.run_until_parked();
20148
20149    cx.assert_state_with_diff(
20150        r#"
20151        use some::mod1;
20152        use some::mod2;
20153
20154        const A: u32 = 42;
20155        const B: u32 = 42;
20156      - const C: u32 = 42;
20157      + const C: u32 = 43ˇ
20158        const D: u32 = 42;
20159
20160
20161        fn main() {
20162            println!("hello");
20163
20164            println!("world");
20165        }"#
20166        .unindent(),
20167    );
20168
20169    cx.update_editor(|editor, window, cx| {
20170        editor.handle_input("\nnew_line\n", window, cx);
20171    });
20172    executor.run_until_parked();
20173
20174    cx.assert_state_with_diff(
20175        r#"
20176        use some::mod1;
20177        use some::mod2;
20178
20179        const A: u32 = 42;
20180        const B: u32 = 42;
20181      - const C: u32 = 42;
20182      + const C: u32 = 43
20183      + new_line
20184      + ˇ
20185        const D: u32 = 42;
20186
20187
20188        fn main() {
20189            println!("hello");
20190
20191            println!("world");
20192        }"#
20193        .unindent(),
20194    );
20195}
20196
20197#[gpui::test]
20198async fn test_stage_and_unstage_added_file_hunk(
20199    executor: BackgroundExecutor,
20200    cx: &mut TestAppContext,
20201) {
20202    init_test(cx, |_| {});
20203
20204    let mut cx = EditorTestContext::new(cx).await;
20205    cx.update_editor(|editor, _, cx| {
20206        editor.set_expand_all_diff_hunks(cx);
20207    });
20208
20209    let working_copy = r#"
20210            ˇfn main() {
20211                println!("hello, world!");
20212            }
20213        "#
20214    .unindent();
20215
20216    cx.set_state(&working_copy);
20217    executor.run_until_parked();
20218
20219    cx.assert_state_with_diff(
20220        r#"
20221            + ˇfn main() {
20222            +     println!("hello, world!");
20223            + }
20224        "#
20225        .unindent(),
20226    );
20227    cx.assert_index_text(None);
20228
20229    cx.update_editor(|editor, window, cx| {
20230        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20231    });
20232    executor.run_until_parked();
20233    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20234    cx.assert_state_with_diff(
20235        r#"
20236            + ˇfn main() {
20237            +     println!("hello, world!");
20238            + }
20239        "#
20240        .unindent(),
20241    );
20242
20243    cx.update_editor(|editor, window, cx| {
20244        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20245    });
20246    executor.run_until_parked();
20247    cx.assert_index_text(None);
20248}
20249
20250async fn setup_indent_guides_editor(
20251    text: &str,
20252    cx: &mut TestAppContext,
20253) -> (BufferId, EditorTestContext) {
20254    init_test(cx, |_| {});
20255
20256    let mut cx = EditorTestContext::new(cx).await;
20257
20258    let buffer_id = cx.update_editor(|editor, window, cx| {
20259        editor.set_text(text, window, cx);
20260        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20261
20262        buffer_ids[0]
20263    });
20264
20265    (buffer_id, cx)
20266}
20267
20268fn assert_indent_guides(
20269    range: Range<u32>,
20270    expected: Vec<IndentGuide>,
20271    active_indices: Option<Vec<usize>>,
20272    cx: &mut EditorTestContext,
20273) {
20274    let indent_guides = cx.update_editor(|editor, window, cx| {
20275        let snapshot = editor.snapshot(window, cx).display_snapshot;
20276        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20277            editor,
20278            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20279            true,
20280            &snapshot,
20281            cx,
20282        );
20283
20284        indent_guides.sort_by(|a, b| {
20285            a.depth.cmp(&b.depth).then(
20286                a.start_row
20287                    .cmp(&b.start_row)
20288                    .then(a.end_row.cmp(&b.end_row)),
20289            )
20290        });
20291        indent_guides
20292    });
20293
20294    if let Some(expected) = active_indices {
20295        let active_indices = cx.update_editor(|editor, window, cx| {
20296            let snapshot = editor.snapshot(window, cx).display_snapshot;
20297            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20298        });
20299
20300        assert_eq!(
20301            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20302            expected,
20303            "Active indent guide indices do not match"
20304        );
20305    }
20306
20307    assert_eq!(indent_guides, expected, "Indent guides do not match");
20308}
20309
20310fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20311    IndentGuide {
20312        buffer_id,
20313        start_row: MultiBufferRow(start_row),
20314        end_row: MultiBufferRow(end_row),
20315        depth,
20316        tab_size: 4,
20317        settings: IndentGuideSettings {
20318            enabled: true,
20319            line_width: 1,
20320            active_line_width: 1,
20321            coloring: IndentGuideColoring::default(),
20322            background_coloring: IndentGuideBackgroundColoring::default(),
20323        },
20324    }
20325}
20326
20327#[gpui::test]
20328async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20329    let (buffer_id, mut cx) = setup_indent_guides_editor(
20330        &"
20331        fn main() {
20332            let a = 1;
20333        }"
20334        .unindent(),
20335        cx,
20336    )
20337    .await;
20338
20339    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20340}
20341
20342#[gpui::test]
20343async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20344    let (buffer_id, mut cx) = setup_indent_guides_editor(
20345        &"
20346        fn main() {
20347            let a = 1;
20348            let b = 2;
20349        }"
20350        .unindent(),
20351        cx,
20352    )
20353    .await;
20354
20355    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20356}
20357
20358#[gpui::test]
20359async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20360    let (buffer_id, mut cx) = setup_indent_guides_editor(
20361        &"
20362        fn main() {
20363            let a = 1;
20364            if a == 3 {
20365                let b = 2;
20366            } else {
20367                let c = 3;
20368            }
20369        }"
20370        .unindent(),
20371        cx,
20372    )
20373    .await;
20374
20375    assert_indent_guides(
20376        0..8,
20377        vec![
20378            indent_guide(buffer_id, 1, 6, 0),
20379            indent_guide(buffer_id, 3, 3, 1),
20380            indent_guide(buffer_id, 5, 5, 1),
20381        ],
20382        None,
20383        &mut cx,
20384    );
20385}
20386
20387#[gpui::test]
20388async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20389    let (buffer_id, mut cx) = setup_indent_guides_editor(
20390        &"
20391        fn main() {
20392            let a = 1;
20393                let b = 2;
20394            let c = 3;
20395        }"
20396        .unindent(),
20397        cx,
20398    )
20399    .await;
20400
20401    assert_indent_guides(
20402        0..5,
20403        vec![
20404            indent_guide(buffer_id, 1, 3, 0),
20405            indent_guide(buffer_id, 2, 2, 1),
20406        ],
20407        None,
20408        &mut cx,
20409    );
20410}
20411
20412#[gpui::test]
20413async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20414    let (buffer_id, mut cx) = setup_indent_guides_editor(
20415        &"
20416        fn main() {
20417            let a = 1;
20418
20419            let c = 3;
20420        }"
20421        .unindent(),
20422        cx,
20423    )
20424    .await;
20425
20426    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20427}
20428
20429#[gpui::test]
20430async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20431    let (buffer_id, mut cx) = setup_indent_guides_editor(
20432        &"
20433        fn main() {
20434            let a = 1;
20435
20436            let c = 3;
20437
20438            if a == 3 {
20439                let b = 2;
20440            } else {
20441                let c = 3;
20442            }
20443        }"
20444        .unindent(),
20445        cx,
20446    )
20447    .await;
20448
20449    assert_indent_guides(
20450        0..11,
20451        vec![
20452            indent_guide(buffer_id, 1, 9, 0),
20453            indent_guide(buffer_id, 6, 6, 1),
20454            indent_guide(buffer_id, 8, 8, 1),
20455        ],
20456        None,
20457        &mut cx,
20458    );
20459}
20460
20461#[gpui::test]
20462async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20463    let (buffer_id, mut cx) = setup_indent_guides_editor(
20464        &"
20465        fn main() {
20466            let a = 1;
20467
20468            let c = 3;
20469
20470            if a == 3 {
20471                let b = 2;
20472            } else {
20473                let c = 3;
20474            }
20475        }"
20476        .unindent(),
20477        cx,
20478    )
20479    .await;
20480
20481    assert_indent_guides(
20482        1..11,
20483        vec![
20484            indent_guide(buffer_id, 1, 9, 0),
20485            indent_guide(buffer_id, 6, 6, 1),
20486            indent_guide(buffer_id, 8, 8, 1),
20487        ],
20488        None,
20489        &mut cx,
20490    );
20491}
20492
20493#[gpui::test]
20494async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20495    let (buffer_id, mut cx) = setup_indent_guides_editor(
20496        &"
20497        fn main() {
20498            let a = 1;
20499
20500            let c = 3;
20501
20502            if a == 3 {
20503                let b = 2;
20504            } else {
20505                let c = 3;
20506            }
20507        }"
20508        .unindent(),
20509        cx,
20510    )
20511    .await;
20512
20513    assert_indent_guides(
20514        1..10,
20515        vec![
20516            indent_guide(buffer_id, 1, 9, 0),
20517            indent_guide(buffer_id, 6, 6, 1),
20518            indent_guide(buffer_id, 8, 8, 1),
20519        ],
20520        None,
20521        &mut cx,
20522    );
20523}
20524
20525#[gpui::test]
20526async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20527    let (buffer_id, mut cx) = setup_indent_guides_editor(
20528        &"
20529        fn main() {
20530            if a {
20531                b(
20532                    c,
20533                    d,
20534                )
20535            } else {
20536                e(
20537                    f
20538                )
20539            }
20540        }"
20541        .unindent(),
20542        cx,
20543    )
20544    .await;
20545
20546    assert_indent_guides(
20547        0..11,
20548        vec![
20549            indent_guide(buffer_id, 1, 10, 0),
20550            indent_guide(buffer_id, 2, 5, 1),
20551            indent_guide(buffer_id, 7, 9, 1),
20552            indent_guide(buffer_id, 3, 4, 2),
20553            indent_guide(buffer_id, 8, 8, 2),
20554        ],
20555        None,
20556        &mut cx,
20557    );
20558
20559    cx.update_editor(|editor, window, cx| {
20560        editor.fold_at(MultiBufferRow(2), window, cx);
20561        assert_eq!(
20562            editor.display_text(cx),
20563            "
20564            fn main() {
20565                if a {
20566                    b(⋯
20567                    )
20568                } else {
20569                    e(
20570                        f
20571                    )
20572                }
20573            }"
20574            .unindent()
20575        );
20576    });
20577
20578    assert_indent_guides(
20579        0..11,
20580        vec![
20581            indent_guide(buffer_id, 1, 10, 0),
20582            indent_guide(buffer_id, 2, 5, 1),
20583            indent_guide(buffer_id, 7, 9, 1),
20584            indent_guide(buffer_id, 8, 8, 2),
20585        ],
20586        None,
20587        &mut cx,
20588    );
20589}
20590
20591#[gpui::test]
20592async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20593    let (buffer_id, mut cx) = setup_indent_guides_editor(
20594        &"
20595        block1
20596            block2
20597                block3
20598                    block4
20599            block2
20600        block1
20601        block1"
20602            .unindent(),
20603        cx,
20604    )
20605    .await;
20606
20607    assert_indent_guides(
20608        1..10,
20609        vec![
20610            indent_guide(buffer_id, 1, 4, 0),
20611            indent_guide(buffer_id, 2, 3, 1),
20612            indent_guide(buffer_id, 3, 3, 2),
20613        ],
20614        None,
20615        &mut cx,
20616    );
20617}
20618
20619#[gpui::test]
20620async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20621    let (buffer_id, mut cx) = setup_indent_guides_editor(
20622        &"
20623        block1
20624            block2
20625                block3
20626
20627        block1
20628        block1"
20629            .unindent(),
20630        cx,
20631    )
20632    .await;
20633
20634    assert_indent_guides(
20635        0..6,
20636        vec![
20637            indent_guide(buffer_id, 1, 2, 0),
20638            indent_guide(buffer_id, 2, 2, 1),
20639        ],
20640        None,
20641        &mut cx,
20642    );
20643}
20644
20645#[gpui::test]
20646async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20647    let (buffer_id, mut cx) = setup_indent_guides_editor(
20648        &"
20649        function component() {
20650        \treturn (
20651        \t\t\t
20652        \t\t<div>
20653        \t\t\t<abc></abc>
20654        \t\t</div>
20655        \t)
20656        }"
20657        .unindent(),
20658        cx,
20659    )
20660    .await;
20661
20662    assert_indent_guides(
20663        0..8,
20664        vec![
20665            indent_guide(buffer_id, 1, 6, 0),
20666            indent_guide(buffer_id, 2, 5, 1),
20667            indent_guide(buffer_id, 4, 4, 2),
20668        ],
20669        None,
20670        &mut cx,
20671    );
20672}
20673
20674#[gpui::test]
20675async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20676    let (buffer_id, mut cx) = setup_indent_guides_editor(
20677        &"
20678        function component() {
20679        \treturn (
20680        \t
20681        \t\t<div>
20682        \t\t\t<abc></abc>
20683        \t\t</div>
20684        \t)
20685        }"
20686        .unindent(),
20687        cx,
20688    )
20689    .await;
20690
20691    assert_indent_guides(
20692        0..8,
20693        vec![
20694            indent_guide(buffer_id, 1, 6, 0),
20695            indent_guide(buffer_id, 2, 5, 1),
20696            indent_guide(buffer_id, 4, 4, 2),
20697        ],
20698        None,
20699        &mut cx,
20700    );
20701}
20702
20703#[gpui::test]
20704async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20705    let (buffer_id, mut cx) = setup_indent_guides_editor(
20706        &"
20707        block1
20708
20709
20710
20711            block2
20712        "
20713        .unindent(),
20714        cx,
20715    )
20716    .await;
20717
20718    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20719}
20720
20721#[gpui::test]
20722async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20723    let (buffer_id, mut cx) = setup_indent_guides_editor(
20724        &"
20725        def a:
20726        \tb = 3
20727        \tif True:
20728        \t\tc = 4
20729        \t\td = 5
20730        \tprint(b)
20731        "
20732        .unindent(),
20733        cx,
20734    )
20735    .await;
20736
20737    assert_indent_guides(
20738        0..6,
20739        vec![
20740            indent_guide(buffer_id, 1, 5, 0),
20741            indent_guide(buffer_id, 3, 4, 1),
20742        ],
20743        None,
20744        &mut cx,
20745    );
20746}
20747
20748#[gpui::test]
20749async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20750    let (buffer_id, mut cx) = setup_indent_guides_editor(
20751        &"
20752    fn main() {
20753        let a = 1;
20754    }"
20755        .unindent(),
20756        cx,
20757    )
20758    .await;
20759
20760    cx.update_editor(|editor, window, cx| {
20761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20762            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20763        });
20764    });
20765
20766    assert_indent_guides(
20767        0..3,
20768        vec![indent_guide(buffer_id, 1, 1, 0)],
20769        Some(vec![0]),
20770        &mut cx,
20771    );
20772}
20773
20774#[gpui::test]
20775async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20776    let (buffer_id, mut cx) = setup_indent_guides_editor(
20777        &"
20778    fn main() {
20779        if 1 == 2 {
20780            let a = 1;
20781        }
20782    }"
20783        .unindent(),
20784        cx,
20785    )
20786    .await;
20787
20788    cx.update_editor(|editor, window, cx| {
20789        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20790            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20791        });
20792    });
20793
20794    assert_indent_guides(
20795        0..4,
20796        vec![
20797            indent_guide(buffer_id, 1, 3, 0),
20798            indent_guide(buffer_id, 2, 2, 1),
20799        ],
20800        Some(vec![1]),
20801        &mut cx,
20802    );
20803
20804    cx.update_editor(|editor, window, cx| {
20805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20806            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20807        });
20808    });
20809
20810    assert_indent_guides(
20811        0..4,
20812        vec![
20813            indent_guide(buffer_id, 1, 3, 0),
20814            indent_guide(buffer_id, 2, 2, 1),
20815        ],
20816        Some(vec![1]),
20817        &mut cx,
20818    );
20819
20820    cx.update_editor(|editor, window, cx| {
20821        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20822            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20823        });
20824    });
20825
20826    assert_indent_guides(
20827        0..4,
20828        vec![
20829            indent_guide(buffer_id, 1, 3, 0),
20830            indent_guide(buffer_id, 2, 2, 1),
20831        ],
20832        Some(vec![0]),
20833        &mut cx,
20834    );
20835}
20836
20837#[gpui::test]
20838async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20839    let (buffer_id, mut cx) = setup_indent_guides_editor(
20840        &"
20841    fn main() {
20842        let a = 1;
20843
20844        let b = 2;
20845    }"
20846        .unindent(),
20847        cx,
20848    )
20849    .await;
20850
20851    cx.update_editor(|editor, window, cx| {
20852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20853            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20854        });
20855    });
20856
20857    assert_indent_guides(
20858        0..5,
20859        vec![indent_guide(buffer_id, 1, 3, 0)],
20860        Some(vec![0]),
20861        &mut cx,
20862    );
20863}
20864
20865#[gpui::test]
20866async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20867    let (buffer_id, mut cx) = setup_indent_guides_editor(
20868        &"
20869    def m:
20870        a = 1
20871        pass"
20872            .unindent(),
20873        cx,
20874    )
20875    .await;
20876
20877    cx.update_editor(|editor, window, cx| {
20878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20879            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20880        });
20881    });
20882
20883    assert_indent_guides(
20884        0..3,
20885        vec![indent_guide(buffer_id, 1, 2, 0)],
20886        Some(vec![0]),
20887        &mut cx,
20888    );
20889}
20890
20891#[gpui::test]
20892async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20893    init_test(cx, |_| {});
20894    let mut cx = EditorTestContext::new(cx).await;
20895    let text = indoc! {
20896        "
20897        impl A {
20898            fn b() {
20899                0;
20900                3;
20901                5;
20902                6;
20903                7;
20904            }
20905        }
20906        "
20907    };
20908    let base_text = indoc! {
20909        "
20910        impl A {
20911            fn b() {
20912                0;
20913                1;
20914                2;
20915                3;
20916                4;
20917            }
20918            fn c() {
20919                5;
20920                6;
20921                7;
20922            }
20923        }
20924        "
20925    };
20926
20927    cx.update_editor(|editor, window, cx| {
20928        editor.set_text(text, window, cx);
20929
20930        editor.buffer().update(cx, |multibuffer, cx| {
20931            let buffer = multibuffer.as_singleton().unwrap();
20932            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20933
20934            multibuffer.set_all_diff_hunks_expanded(cx);
20935            multibuffer.add_diff(diff, cx);
20936
20937            buffer.read(cx).remote_id()
20938        })
20939    });
20940    cx.run_until_parked();
20941
20942    cx.assert_state_with_diff(
20943        indoc! { "
20944          impl A {
20945              fn b() {
20946                  0;
20947        -         1;
20948        -         2;
20949                  3;
20950        -         4;
20951        -     }
20952        -     fn c() {
20953                  5;
20954                  6;
20955                  7;
20956              }
20957          }
20958          ˇ"
20959        }
20960        .to_string(),
20961    );
20962
20963    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20964        editor
20965            .snapshot(window, cx)
20966            .buffer_snapshot()
20967            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20968            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20969            .collect::<Vec<_>>()
20970    });
20971    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20972    assert_eq!(
20973        actual_guides,
20974        vec![
20975            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20976            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20977            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20978        ]
20979    );
20980}
20981
20982#[gpui::test]
20983async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20984    init_test(cx, |_| {});
20985    let mut cx = EditorTestContext::new(cx).await;
20986
20987    let diff_base = r#"
20988        a
20989        b
20990        c
20991        "#
20992    .unindent();
20993
20994    cx.set_state(
20995        &r#"
20996        ˇA
20997        b
20998        C
20999        "#
21000        .unindent(),
21001    );
21002    cx.set_head_text(&diff_base);
21003    cx.update_editor(|editor, window, cx| {
21004        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21005    });
21006    executor.run_until_parked();
21007
21008    let both_hunks_expanded = r#"
21009        - a
21010        + ˇA
21011          b
21012        - c
21013        + C
21014        "#
21015    .unindent();
21016
21017    cx.assert_state_with_diff(both_hunks_expanded.clone());
21018
21019    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21020        let snapshot = editor.snapshot(window, cx);
21021        let hunks = editor
21022            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21023            .collect::<Vec<_>>();
21024        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21025        let buffer_id = hunks[0].buffer_id;
21026        hunks
21027            .into_iter()
21028            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21029            .collect::<Vec<_>>()
21030    });
21031    assert_eq!(hunk_ranges.len(), 2);
21032
21033    cx.update_editor(|editor, _, cx| {
21034        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21035    });
21036    executor.run_until_parked();
21037
21038    let second_hunk_expanded = r#"
21039          ˇA
21040          b
21041        - c
21042        + C
21043        "#
21044    .unindent();
21045
21046    cx.assert_state_with_diff(second_hunk_expanded);
21047
21048    cx.update_editor(|editor, _, cx| {
21049        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21050    });
21051    executor.run_until_parked();
21052
21053    cx.assert_state_with_diff(both_hunks_expanded.clone());
21054
21055    cx.update_editor(|editor, _, cx| {
21056        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21057    });
21058    executor.run_until_parked();
21059
21060    let first_hunk_expanded = r#"
21061        - a
21062        + ˇA
21063          b
21064          C
21065        "#
21066    .unindent();
21067
21068    cx.assert_state_with_diff(first_hunk_expanded);
21069
21070    cx.update_editor(|editor, _, cx| {
21071        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21072    });
21073    executor.run_until_parked();
21074
21075    cx.assert_state_with_diff(both_hunks_expanded);
21076
21077    cx.set_state(
21078        &r#"
21079        ˇA
21080        b
21081        "#
21082        .unindent(),
21083    );
21084    cx.run_until_parked();
21085
21086    // TODO this cursor position seems bad
21087    cx.assert_state_with_diff(
21088        r#"
21089        - ˇa
21090        + A
21091          b
21092        "#
21093        .unindent(),
21094    );
21095
21096    cx.update_editor(|editor, window, cx| {
21097        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21098    });
21099
21100    cx.assert_state_with_diff(
21101        r#"
21102            - ˇa
21103            + A
21104              b
21105            - c
21106            "#
21107        .unindent(),
21108    );
21109
21110    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21111        let snapshot = editor.snapshot(window, cx);
21112        let hunks = editor
21113            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21114            .collect::<Vec<_>>();
21115        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21116        let buffer_id = hunks[0].buffer_id;
21117        hunks
21118            .into_iter()
21119            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21120            .collect::<Vec<_>>()
21121    });
21122    assert_eq!(hunk_ranges.len(), 2);
21123
21124    cx.update_editor(|editor, _, cx| {
21125        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21126    });
21127    executor.run_until_parked();
21128
21129    cx.assert_state_with_diff(
21130        r#"
21131        - ˇa
21132        + A
21133          b
21134        "#
21135        .unindent(),
21136    );
21137}
21138
21139#[gpui::test]
21140async fn test_toggle_deletion_hunk_at_start_of_file(
21141    executor: BackgroundExecutor,
21142    cx: &mut TestAppContext,
21143) {
21144    init_test(cx, |_| {});
21145    let mut cx = EditorTestContext::new(cx).await;
21146
21147    let diff_base = r#"
21148        a
21149        b
21150        c
21151        "#
21152    .unindent();
21153
21154    cx.set_state(
21155        &r#"
21156        ˇb
21157        c
21158        "#
21159        .unindent(),
21160    );
21161    cx.set_head_text(&diff_base);
21162    cx.update_editor(|editor, window, cx| {
21163        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21164    });
21165    executor.run_until_parked();
21166
21167    let hunk_expanded = r#"
21168        - a
21169          ˇb
21170          c
21171        "#
21172    .unindent();
21173
21174    cx.assert_state_with_diff(hunk_expanded.clone());
21175
21176    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21177        let snapshot = editor.snapshot(window, cx);
21178        let hunks = editor
21179            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21180            .collect::<Vec<_>>();
21181        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21182        let buffer_id = hunks[0].buffer_id;
21183        hunks
21184            .into_iter()
21185            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21186            .collect::<Vec<_>>()
21187    });
21188    assert_eq!(hunk_ranges.len(), 1);
21189
21190    cx.update_editor(|editor, _, cx| {
21191        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21192    });
21193    executor.run_until_parked();
21194
21195    let hunk_collapsed = r#"
21196          ˇb
21197          c
21198        "#
21199    .unindent();
21200
21201    cx.assert_state_with_diff(hunk_collapsed);
21202
21203    cx.update_editor(|editor, _, cx| {
21204        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21205    });
21206    executor.run_until_parked();
21207
21208    cx.assert_state_with_diff(hunk_expanded);
21209}
21210
21211#[gpui::test]
21212async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21213    init_test(cx, |_| {});
21214
21215    let fs = FakeFs::new(cx.executor());
21216    fs.insert_tree(
21217        path!("/test"),
21218        json!({
21219            ".git": {},
21220            "file-1": "ONE\n",
21221            "file-2": "TWO\n",
21222            "file-3": "THREE\n",
21223        }),
21224    )
21225    .await;
21226
21227    fs.set_head_for_repo(
21228        path!("/test/.git").as_ref(),
21229        &[
21230            ("file-1", "one\n".into()),
21231            ("file-2", "two\n".into()),
21232            ("file-3", "three\n".into()),
21233        ],
21234        "deadbeef",
21235    );
21236
21237    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21238    let mut buffers = vec![];
21239    for i in 1..=3 {
21240        let buffer = project
21241            .update(cx, |project, cx| {
21242                let path = format!(path!("/test/file-{}"), i);
21243                project.open_local_buffer(path, cx)
21244            })
21245            .await
21246            .unwrap();
21247        buffers.push(buffer);
21248    }
21249
21250    let multibuffer = cx.new(|cx| {
21251        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21252        multibuffer.set_all_diff_hunks_expanded(cx);
21253        for buffer in &buffers {
21254            let snapshot = buffer.read(cx).snapshot();
21255            multibuffer.set_excerpts_for_path(
21256                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21257                buffer.clone(),
21258                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21259                2,
21260                cx,
21261            );
21262        }
21263        multibuffer
21264    });
21265
21266    let editor = cx.add_window(|window, cx| {
21267        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21268    });
21269    cx.run_until_parked();
21270
21271    let snapshot = editor
21272        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21273        .unwrap();
21274    let hunks = snapshot
21275        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21276        .map(|hunk| match hunk {
21277            DisplayDiffHunk::Unfolded {
21278                display_row_range, ..
21279            } => display_row_range,
21280            DisplayDiffHunk::Folded { .. } => unreachable!(),
21281        })
21282        .collect::<Vec<_>>();
21283    assert_eq!(
21284        hunks,
21285        [
21286            DisplayRow(2)..DisplayRow(4),
21287            DisplayRow(7)..DisplayRow(9),
21288            DisplayRow(12)..DisplayRow(14),
21289        ]
21290    );
21291}
21292
21293#[gpui::test]
21294async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21295    init_test(cx, |_| {});
21296
21297    let mut cx = EditorTestContext::new(cx).await;
21298    cx.set_head_text(indoc! { "
21299        one
21300        two
21301        three
21302        four
21303        five
21304        "
21305    });
21306    cx.set_index_text(indoc! { "
21307        one
21308        two
21309        three
21310        four
21311        five
21312        "
21313    });
21314    cx.set_state(indoc! {"
21315        one
21316        TWO
21317        ˇTHREE
21318        FOUR
21319        five
21320    "});
21321    cx.run_until_parked();
21322    cx.update_editor(|editor, window, cx| {
21323        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21324    });
21325    cx.run_until_parked();
21326    cx.assert_index_text(Some(indoc! {"
21327        one
21328        TWO
21329        THREE
21330        FOUR
21331        five
21332    "}));
21333    cx.set_state(indoc! { "
21334        one
21335        TWO
21336        ˇTHREE-HUNDRED
21337        FOUR
21338        five
21339    "});
21340    cx.run_until_parked();
21341    cx.update_editor(|editor, window, cx| {
21342        let snapshot = editor.snapshot(window, cx);
21343        let hunks = editor
21344            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21345            .collect::<Vec<_>>();
21346        assert_eq!(hunks.len(), 1);
21347        assert_eq!(
21348            hunks[0].status(),
21349            DiffHunkStatus {
21350                kind: DiffHunkStatusKind::Modified,
21351                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21352            }
21353        );
21354
21355        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21356    });
21357    cx.run_until_parked();
21358    cx.assert_index_text(Some(indoc! {"
21359        one
21360        TWO
21361        THREE-HUNDRED
21362        FOUR
21363        five
21364    "}));
21365}
21366
21367#[gpui::test]
21368fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21369    init_test(cx, |_| {});
21370
21371    let editor = cx.add_window(|window, cx| {
21372        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21373        build_editor(buffer, window, cx)
21374    });
21375
21376    let render_args = Arc::new(Mutex::new(None));
21377    let snapshot = editor
21378        .update(cx, |editor, window, cx| {
21379            let snapshot = editor.buffer().read(cx).snapshot(cx);
21380            let range =
21381                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21382
21383            struct RenderArgs {
21384                row: MultiBufferRow,
21385                folded: bool,
21386                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21387            }
21388
21389            let crease = Crease::inline(
21390                range,
21391                FoldPlaceholder::test(),
21392                {
21393                    let toggle_callback = render_args.clone();
21394                    move |row, folded, callback, _window, _cx| {
21395                        *toggle_callback.lock() = Some(RenderArgs {
21396                            row,
21397                            folded,
21398                            callback,
21399                        });
21400                        div()
21401                    }
21402                },
21403                |_row, _folded, _window, _cx| div(),
21404            );
21405
21406            editor.insert_creases(Some(crease), cx);
21407            let snapshot = editor.snapshot(window, cx);
21408            let _div =
21409                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21410            snapshot
21411        })
21412        .unwrap();
21413
21414    let render_args = render_args.lock().take().unwrap();
21415    assert_eq!(render_args.row, MultiBufferRow(1));
21416    assert!(!render_args.folded);
21417    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21418
21419    cx.update_window(*editor, |_, window, cx| {
21420        (render_args.callback)(true, window, cx)
21421    })
21422    .unwrap();
21423    let snapshot = editor
21424        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21425        .unwrap();
21426    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21427
21428    cx.update_window(*editor, |_, window, cx| {
21429        (render_args.callback)(false, window, cx)
21430    })
21431    .unwrap();
21432    let snapshot = editor
21433        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21434        .unwrap();
21435    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21436}
21437
21438#[gpui::test]
21439async fn test_input_text(cx: &mut TestAppContext) {
21440    init_test(cx, |_| {});
21441    let mut cx = EditorTestContext::new(cx).await;
21442
21443    cx.set_state(
21444        &r#"ˇone
21445        two
21446
21447        three
21448        fourˇ
21449        five
21450
21451        siˇx"#
21452            .unindent(),
21453    );
21454
21455    cx.dispatch_action(HandleInput(String::new()));
21456    cx.assert_editor_state(
21457        &r#"ˇone
21458        two
21459
21460        three
21461        fourˇ
21462        five
21463
21464        siˇx"#
21465            .unindent(),
21466    );
21467
21468    cx.dispatch_action(HandleInput("AAAA".to_string()));
21469    cx.assert_editor_state(
21470        &r#"AAAAˇone
21471        two
21472
21473        three
21474        fourAAAAˇ
21475        five
21476
21477        siAAAAˇx"#
21478            .unindent(),
21479    );
21480}
21481
21482#[gpui::test]
21483async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21484    init_test(cx, |_| {});
21485
21486    let mut cx = EditorTestContext::new(cx).await;
21487    cx.set_state(
21488        r#"let foo = 1;
21489let foo = 2;
21490let foo = 3;
21491let fooˇ = 4;
21492let foo = 5;
21493let foo = 6;
21494let foo = 7;
21495let foo = 8;
21496let foo = 9;
21497let foo = 10;
21498let foo = 11;
21499let foo = 12;
21500let foo = 13;
21501let foo = 14;
21502let foo = 15;"#,
21503    );
21504
21505    cx.update_editor(|e, window, cx| {
21506        assert_eq!(
21507            e.next_scroll_position,
21508            NextScrollCursorCenterTopBottom::Center,
21509            "Default next scroll direction is center",
21510        );
21511
21512        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21513        assert_eq!(
21514            e.next_scroll_position,
21515            NextScrollCursorCenterTopBottom::Top,
21516            "After center, next scroll direction should be top",
21517        );
21518
21519        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21520        assert_eq!(
21521            e.next_scroll_position,
21522            NextScrollCursorCenterTopBottom::Bottom,
21523            "After top, next scroll direction should be bottom",
21524        );
21525
21526        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21527        assert_eq!(
21528            e.next_scroll_position,
21529            NextScrollCursorCenterTopBottom::Center,
21530            "After bottom, scrolling should start over",
21531        );
21532
21533        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21534        assert_eq!(
21535            e.next_scroll_position,
21536            NextScrollCursorCenterTopBottom::Top,
21537            "Scrolling continues if retriggered fast enough"
21538        );
21539    });
21540
21541    cx.executor()
21542        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21543    cx.executor().run_until_parked();
21544    cx.update_editor(|e, _, _| {
21545        assert_eq!(
21546            e.next_scroll_position,
21547            NextScrollCursorCenterTopBottom::Center,
21548            "If scrolling is not triggered fast enough, it should reset"
21549        );
21550    });
21551}
21552
21553#[gpui::test]
21554async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21555    init_test(cx, |_| {});
21556    let mut cx = EditorLspTestContext::new_rust(
21557        lsp::ServerCapabilities {
21558            definition_provider: Some(lsp::OneOf::Left(true)),
21559            references_provider: Some(lsp::OneOf::Left(true)),
21560            ..lsp::ServerCapabilities::default()
21561        },
21562        cx,
21563    )
21564    .await;
21565
21566    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21567        let go_to_definition = cx
21568            .lsp
21569            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21570                move |params, _| async move {
21571                    if empty_go_to_definition {
21572                        Ok(None)
21573                    } else {
21574                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21575                            uri: params.text_document_position_params.text_document.uri,
21576                            range: lsp::Range::new(
21577                                lsp::Position::new(4, 3),
21578                                lsp::Position::new(4, 6),
21579                            ),
21580                        })))
21581                    }
21582                },
21583            );
21584        let references = cx
21585            .lsp
21586            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21587                Ok(Some(vec![lsp::Location {
21588                    uri: params.text_document_position.text_document.uri,
21589                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21590                }]))
21591            });
21592        (go_to_definition, references)
21593    };
21594
21595    cx.set_state(
21596        &r#"fn one() {
21597            let mut a = ˇtwo();
21598        }
21599
21600        fn two() {}"#
21601            .unindent(),
21602    );
21603    set_up_lsp_handlers(false, &mut cx);
21604    let navigated = cx
21605        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21606        .await
21607        .expect("Failed to navigate to definition");
21608    assert_eq!(
21609        navigated,
21610        Navigated::Yes,
21611        "Should have navigated to definition from the GetDefinition response"
21612    );
21613    cx.assert_editor_state(
21614        &r#"fn one() {
21615            let mut a = two();
21616        }
21617
21618        fn «twoˇ»() {}"#
21619            .unindent(),
21620    );
21621
21622    let editors = cx.update_workspace(|workspace, _, cx| {
21623        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21624    });
21625    cx.update_editor(|_, _, test_editor_cx| {
21626        assert_eq!(
21627            editors.len(),
21628            1,
21629            "Initially, only one, test, editor should be open in the workspace"
21630        );
21631        assert_eq!(
21632            test_editor_cx.entity(),
21633            editors.last().expect("Asserted len is 1").clone()
21634        );
21635    });
21636
21637    set_up_lsp_handlers(true, &mut cx);
21638    let navigated = cx
21639        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21640        .await
21641        .expect("Failed to navigate to lookup references");
21642    assert_eq!(
21643        navigated,
21644        Navigated::Yes,
21645        "Should have navigated to references as a fallback after empty GoToDefinition response"
21646    );
21647    // We should not change the selections in the existing file,
21648    // if opening another milti buffer with the references
21649    cx.assert_editor_state(
21650        &r#"fn one() {
21651            let mut a = two();
21652        }
21653
21654        fn «twoˇ»() {}"#
21655            .unindent(),
21656    );
21657    let editors = cx.update_workspace(|workspace, _, cx| {
21658        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21659    });
21660    cx.update_editor(|_, _, test_editor_cx| {
21661        assert_eq!(
21662            editors.len(),
21663            2,
21664            "After falling back to references search, we open a new editor with the results"
21665        );
21666        let references_fallback_text = editors
21667            .into_iter()
21668            .find(|new_editor| *new_editor != test_editor_cx.entity())
21669            .expect("Should have one non-test editor now")
21670            .read(test_editor_cx)
21671            .text(test_editor_cx);
21672        assert_eq!(
21673            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21674            "Should use the range from the references response and not the GoToDefinition one"
21675        );
21676    });
21677}
21678
21679#[gpui::test]
21680async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21681    init_test(cx, |_| {});
21682    cx.update(|cx| {
21683        let mut editor_settings = EditorSettings::get_global(cx).clone();
21684        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21685        EditorSettings::override_global(editor_settings, cx);
21686    });
21687    let mut cx = EditorLspTestContext::new_rust(
21688        lsp::ServerCapabilities {
21689            definition_provider: Some(lsp::OneOf::Left(true)),
21690            references_provider: Some(lsp::OneOf::Left(true)),
21691            ..lsp::ServerCapabilities::default()
21692        },
21693        cx,
21694    )
21695    .await;
21696    let original_state = r#"fn one() {
21697        let mut a = ˇtwo();
21698    }
21699
21700    fn two() {}"#
21701        .unindent();
21702    cx.set_state(&original_state);
21703
21704    let mut go_to_definition = cx
21705        .lsp
21706        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21707            move |_, _| async move { Ok(None) },
21708        );
21709    let _references = cx
21710        .lsp
21711        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21712            panic!("Should not call for references with no go to definition fallback")
21713        });
21714
21715    let navigated = cx
21716        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21717        .await
21718        .expect("Failed to navigate to lookup references");
21719    go_to_definition
21720        .next()
21721        .await
21722        .expect("Should have called the go_to_definition handler");
21723
21724    assert_eq!(
21725        navigated,
21726        Navigated::No,
21727        "Should have navigated to references as a fallback after empty GoToDefinition response"
21728    );
21729    cx.assert_editor_state(&original_state);
21730    let editors = cx.update_workspace(|workspace, _, cx| {
21731        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21732    });
21733    cx.update_editor(|_, _, _| {
21734        assert_eq!(
21735            editors.len(),
21736            1,
21737            "After unsuccessful fallback, no other editor should have been opened"
21738        );
21739    });
21740}
21741
21742#[gpui::test]
21743async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21744    init_test(cx, |_| {});
21745    let mut cx = EditorLspTestContext::new_rust(
21746        lsp::ServerCapabilities {
21747            references_provider: Some(lsp::OneOf::Left(true)),
21748            ..lsp::ServerCapabilities::default()
21749        },
21750        cx,
21751    )
21752    .await;
21753
21754    cx.set_state(
21755        &r#"
21756        fn one() {
21757            let mut a = two();
21758        }
21759
21760        fn ˇtwo() {}"#
21761            .unindent(),
21762    );
21763    cx.lsp
21764        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21765            Ok(Some(vec![
21766                lsp::Location {
21767                    uri: params.text_document_position.text_document.uri.clone(),
21768                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21769                },
21770                lsp::Location {
21771                    uri: params.text_document_position.text_document.uri,
21772                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21773                },
21774            ]))
21775        });
21776    let navigated = cx
21777        .update_editor(|editor, window, cx| {
21778            editor.find_all_references(&FindAllReferences, window, cx)
21779        })
21780        .unwrap()
21781        .await
21782        .expect("Failed to navigate to references");
21783    assert_eq!(
21784        navigated,
21785        Navigated::Yes,
21786        "Should have navigated to references from the FindAllReferences response"
21787    );
21788    cx.assert_editor_state(
21789        &r#"fn one() {
21790            let mut a = two();
21791        }
21792
21793        fn ˇtwo() {}"#
21794            .unindent(),
21795    );
21796
21797    let editors = cx.update_workspace(|workspace, _, cx| {
21798        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21799    });
21800    cx.update_editor(|_, _, _| {
21801        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21802    });
21803
21804    cx.set_state(
21805        &r#"fn one() {
21806            let mut a = ˇtwo();
21807        }
21808
21809        fn two() {}"#
21810            .unindent(),
21811    );
21812    let navigated = cx
21813        .update_editor(|editor, window, cx| {
21814            editor.find_all_references(&FindAllReferences, window, cx)
21815        })
21816        .unwrap()
21817        .await
21818        .expect("Failed to navigate to references");
21819    assert_eq!(
21820        navigated,
21821        Navigated::Yes,
21822        "Should have navigated to references from the FindAllReferences response"
21823    );
21824    cx.assert_editor_state(
21825        &r#"fn one() {
21826            let mut a = ˇtwo();
21827        }
21828
21829        fn two() {}"#
21830            .unindent(),
21831    );
21832    let editors = cx.update_workspace(|workspace, _, cx| {
21833        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21834    });
21835    cx.update_editor(|_, _, _| {
21836        assert_eq!(
21837            editors.len(),
21838            2,
21839            "should have re-used the previous multibuffer"
21840        );
21841    });
21842
21843    cx.set_state(
21844        &r#"fn one() {
21845            let mut a = ˇtwo();
21846        }
21847        fn three() {}
21848        fn two() {}"#
21849            .unindent(),
21850    );
21851    cx.lsp
21852        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21853            Ok(Some(vec![
21854                lsp::Location {
21855                    uri: params.text_document_position.text_document.uri.clone(),
21856                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21857                },
21858                lsp::Location {
21859                    uri: params.text_document_position.text_document.uri,
21860                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21861                },
21862            ]))
21863        });
21864    let navigated = cx
21865        .update_editor(|editor, window, cx| {
21866            editor.find_all_references(&FindAllReferences, window, cx)
21867        })
21868        .unwrap()
21869        .await
21870        .expect("Failed to navigate to references");
21871    assert_eq!(
21872        navigated,
21873        Navigated::Yes,
21874        "Should have navigated to references from the FindAllReferences response"
21875    );
21876    cx.assert_editor_state(
21877        &r#"fn one() {
21878                let mut a = ˇtwo();
21879            }
21880            fn three() {}
21881            fn two() {}"#
21882            .unindent(),
21883    );
21884    let editors = cx.update_workspace(|workspace, _, cx| {
21885        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21886    });
21887    cx.update_editor(|_, _, _| {
21888        assert_eq!(
21889            editors.len(),
21890            3,
21891            "should have used a new multibuffer as offsets changed"
21892        );
21893    });
21894}
21895#[gpui::test]
21896async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21897    init_test(cx, |_| {});
21898
21899    let language = Arc::new(Language::new(
21900        LanguageConfig::default(),
21901        Some(tree_sitter_rust::LANGUAGE.into()),
21902    ));
21903
21904    let text = r#"
21905        #[cfg(test)]
21906        mod tests() {
21907            #[test]
21908            fn runnable_1() {
21909                let a = 1;
21910            }
21911
21912            #[test]
21913            fn runnable_2() {
21914                let a = 1;
21915                let b = 2;
21916            }
21917        }
21918    "#
21919    .unindent();
21920
21921    let fs = FakeFs::new(cx.executor());
21922    fs.insert_file("/file.rs", Default::default()).await;
21923
21924    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21925    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21926    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21927    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21928    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21929
21930    let editor = cx.new_window_entity(|window, cx| {
21931        Editor::new(
21932            EditorMode::full(),
21933            multi_buffer,
21934            Some(project.clone()),
21935            window,
21936            cx,
21937        )
21938    });
21939
21940    editor.update_in(cx, |editor, window, cx| {
21941        let snapshot = editor.buffer().read(cx).snapshot(cx);
21942        editor.tasks.insert(
21943            (buffer.read(cx).remote_id(), 3),
21944            RunnableTasks {
21945                templates: vec![],
21946                offset: snapshot.anchor_before(43),
21947                column: 0,
21948                extra_variables: HashMap::default(),
21949                context_range: BufferOffset(43)..BufferOffset(85),
21950            },
21951        );
21952        editor.tasks.insert(
21953            (buffer.read(cx).remote_id(), 8),
21954            RunnableTasks {
21955                templates: vec![],
21956                offset: snapshot.anchor_before(86),
21957                column: 0,
21958                extra_variables: HashMap::default(),
21959                context_range: BufferOffset(86)..BufferOffset(191),
21960            },
21961        );
21962
21963        // Test finding task when cursor is inside function body
21964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21965            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21966        });
21967        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21968        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21969
21970        // Test finding task when cursor is on function name
21971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21972            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21973        });
21974        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21975        assert_eq!(row, 8, "Should find task when cursor is on function name");
21976    });
21977}
21978
21979#[gpui::test]
21980async fn test_folding_buffers(cx: &mut TestAppContext) {
21981    init_test(cx, |_| {});
21982
21983    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21984    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21985    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21986
21987    let fs = FakeFs::new(cx.executor());
21988    fs.insert_tree(
21989        path!("/a"),
21990        json!({
21991            "first.rs": sample_text_1,
21992            "second.rs": sample_text_2,
21993            "third.rs": sample_text_3,
21994        }),
21995    )
21996    .await;
21997    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21998    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21999    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22000    let worktree = project.update(cx, |project, cx| {
22001        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22002        assert_eq!(worktrees.len(), 1);
22003        worktrees.pop().unwrap()
22004    });
22005    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22006
22007    let buffer_1 = project
22008        .update(cx, |project, cx| {
22009            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22010        })
22011        .await
22012        .unwrap();
22013    let buffer_2 = project
22014        .update(cx, |project, cx| {
22015            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22016        })
22017        .await
22018        .unwrap();
22019    let buffer_3 = project
22020        .update(cx, |project, cx| {
22021            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22022        })
22023        .await
22024        .unwrap();
22025
22026    let multi_buffer = cx.new(|cx| {
22027        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22028        multi_buffer.push_excerpts(
22029            buffer_1.clone(),
22030            [
22031                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22032                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22033                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22034            ],
22035            cx,
22036        );
22037        multi_buffer.push_excerpts(
22038            buffer_2.clone(),
22039            [
22040                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22041                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22042                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22043            ],
22044            cx,
22045        );
22046        multi_buffer.push_excerpts(
22047            buffer_3.clone(),
22048            [
22049                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22050                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22051                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22052            ],
22053            cx,
22054        );
22055        multi_buffer
22056    });
22057    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22058        Editor::new(
22059            EditorMode::full(),
22060            multi_buffer.clone(),
22061            Some(project.clone()),
22062            window,
22063            cx,
22064        )
22065    });
22066
22067    assert_eq!(
22068        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22069        "\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",
22070    );
22071
22072    multi_buffer_editor.update(cx, |editor, cx| {
22073        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22074    });
22075    assert_eq!(
22076        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22077        "\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",
22078        "After folding the first buffer, its text should not be displayed"
22079    );
22080
22081    multi_buffer_editor.update(cx, |editor, cx| {
22082        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22083    });
22084    assert_eq!(
22085        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22086        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22087        "After folding the second buffer, its text should not be displayed"
22088    );
22089
22090    multi_buffer_editor.update(cx, |editor, cx| {
22091        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22092    });
22093    assert_eq!(
22094        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22095        "\n\n\n\n\n",
22096        "After folding the third buffer, its text should not be displayed"
22097    );
22098
22099    // Emulate selection inside the fold logic, that should work
22100    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22101        editor
22102            .snapshot(window, cx)
22103            .next_line_boundary(Point::new(0, 4));
22104    });
22105
22106    multi_buffer_editor.update(cx, |editor, cx| {
22107        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22108    });
22109    assert_eq!(
22110        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22111        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22112        "After unfolding the second buffer, its text should be displayed"
22113    );
22114
22115    // Typing inside of buffer 1 causes that buffer to be unfolded.
22116    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22117        assert_eq!(
22118            multi_buffer
22119                .read(cx)
22120                .snapshot(cx)
22121                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22122                .collect::<String>(),
22123            "bbbb"
22124        );
22125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22126            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22127        });
22128        editor.handle_input("B", window, cx);
22129    });
22130
22131    assert_eq!(
22132        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22133        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22134        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22135    );
22136
22137    multi_buffer_editor.update(cx, |editor, cx| {
22138        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22139    });
22140    assert_eq!(
22141        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22142        "\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",
22143        "After unfolding the all buffers, all original text should be displayed"
22144    );
22145}
22146
22147#[gpui::test]
22148async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22149    init_test(cx, |_| {});
22150
22151    let sample_text_1 = "1111\n2222\n3333".to_string();
22152    let sample_text_2 = "4444\n5555\n6666".to_string();
22153    let sample_text_3 = "7777\n8888\n9999".to_string();
22154
22155    let fs = FakeFs::new(cx.executor());
22156    fs.insert_tree(
22157        path!("/a"),
22158        json!({
22159            "first.rs": sample_text_1,
22160            "second.rs": sample_text_2,
22161            "third.rs": sample_text_3,
22162        }),
22163    )
22164    .await;
22165    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22166    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22167    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22168    let worktree = project.update(cx, |project, cx| {
22169        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22170        assert_eq!(worktrees.len(), 1);
22171        worktrees.pop().unwrap()
22172    });
22173    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22174
22175    let buffer_1 = project
22176        .update(cx, |project, cx| {
22177            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22178        })
22179        .await
22180        .unwrap();
22181    let buffer_2 = project
22182        .update(cx, |project, cx| {
22183            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22184        })
22185        .await
22186        .unwrap();
22187    let buffer_3 = project
22188        .update(cx, |project, cx| {
22189            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22190        })
22191        .await
22192        .unwrap();
22193
22194    let multi_buffer = cx.new(|cx| {
22195        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22196        multi_buffer.push_excerpts(
22197            buffer_1.clone(),
22198            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22199            cx,
22200        );
22201        multi_buffer.push_excerpts(
22202            buffer_2.clone(),
22203            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22204            cx,
22205        );
22206        multi_buffer.push_excerpts(
22207            buffer_3.clone(),
22208            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22209            cx,
22210        );
22211        multi_buffer
22212    });
22213
22214    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22215        Editor::new(
22216            EditorMode::full(),
22217            multi_buffer,
22218            Some(project.clone()),
22219            window,
22220            cx,
22221        )
22222    });
22223
22224    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22225    assert_eq!(
22226        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22227        full_text,
22228    );
22229
22230    multi_buffer_editor.update(cx, |editor, cx| {
22231        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22232    });
22233    assert_eq!(
22234        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22235        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22236        "After folding the first buffer, its text should not be displayed"
22237    );
22238
22239    multi_buffer_editor.update(cx, |editor, cx| {
22240        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22241    });
22242
22243    assert_eq!(
22244        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22245        "\n\n\n\n\n\n7777\n8888\n9999",
22246        "After folding the second buffer, its text should not be displayed"
22247    );
22248
22249    multi_buffer_editor.update(cx, |editor, cx| {
22250        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22251    });
22252    assert_eq!(
22253        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22254        "\n\n\n\n\n",
22255        "After folding the third buffer, its text should not be displayed"
22256    );
22257
22258    multi_buffer_editor.update(cx, |editor, cx| {
22259        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22260    });
22261    assert_eq!(
22262        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22263        "\n\n\n\n4444\n5555\n6666\n\n",
22264        "After unfolding the second buffer, its text should be displayed"
22265    );
22266
22267    multi_buffer_editor.update(cx, |editor, cx| {
22268        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22269    });
22270    assert_eq!(
22271        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22272        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22273        "After unfolding the first buffer, its text should be displayed"
22274    );
22275
22276    multi_buffer_editor.update(cx, |editor, cx| {
22277        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22278    });
22279    assert_eq!(
22280        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22281        full_text,
22282        "After unfolding all buffers, all original text should be displayed"
22283    );
22284}
22285
22286#[gpui::test]
22287async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22288    init_test(cx, |_| {});
22289
22290    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22291
22292    let fs = FakeFs::new(cx.executor());
22293    fs.insert_tree(
22294        path!("/a"),
22295        json!({
22296            "main.rs": sample_text,
22297        }),
22298    )
22299    .await;
22300    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22301    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22302    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22303    let worktree = project.update(cx, |project, cx| {
22304        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22305        assert_eq!(worktrees.len(), 1);
22306        worktrees.pop().unwrap()
22307    });
22308    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22309
22310    let buffer_1 = project
22311        .update(cx, |project, cx| {
22312            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22313        })
22314        .await
22315        .unwrap();
22316
22317    let multi_buffer = cx.new(|cx| {
22318        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22319        multi_buffer.push_excerpts(
22320            buffer_1.clone(),
22321            [ExcerptRange::new(
22322                Point::new(0, 0)
22323                    ..Point::new(
22324                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22325                        0,
22326                    ),
22327            )],
22328            cx,
22329        );
22330        multi_buffer
22331    });
22332    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22333        Editor::new(
22334            EditorMode::full(),
22335            multi_buffer,
22336            Some(project.clone()),
22337            window,
22338            cx,
22339        )
22340    });
22341
22342    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22343    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22344        enum TestHighlight {}
22345        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22346        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22347        editor.highlight_text::<TestHighlight>(
22348            vec![highlight_range.clone()],
22349            HighlightStyle::color(Hsla::green()),
22350            cx,
22351        );
22352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22353            s.select_ranges(Some(highlight_range))
22354        });
22355    });
22356
22357    let full_text = format!("\n\n{sample_text}");
22358    assert_eq!(
22359        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22360        full_text,
22361    );
22362}
22363
22364#[gpui::test]
22365async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22366    init_test(cx, |_| {});
22367    cx.update(|cx| {
22368        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22369            "keymaps/default-linux.json",
22370            cx,
22371        )
22372        .unwrap();
22373        cx.bind_keys(default_key_bindings);
22374    });
22375
22376    let (editor, cx) = cx.add_window_view(|window, cx| {
22377        let multi_buffer = MultiBuffer::build_multi(
22378            [
22379                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22380                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22381                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22382                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22383            ],
22384            cx,
22385        );
22386        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22387
22388        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22389        // fold all but the second buffer, so that we test navigating between two
22390        // adjacent folded buffers, as well as folded buffers at the start and
22391        // end the multibuffer
22392        editor.fold_buffer(buffer_ids[0], cx);
22393        editor.fold_buffer(buffer_ids[2], cx);
22394        editor.fold_buffer(buffer_ids[3], cx);
22395
22396        editor
22397    });
22398    cx.simulate_resize(size(px(1000.), px(1000.)));
22399
22400    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22401    cx.assert_excerpts_with_selections(indoc! {"
22402        [EXCERPT]
22403        ˇ[FOLDED]
22404        [EXCERPT]
22405        a1
22406        b1
22407        [EXCERPT]
22408        [FOLDED]
22409        [EXCERPT]
22410        [FOLDED]
22411        "
22412    });
22413    cx.simulate_keystroke("down");
22414    cx.assert_excerpts_with_selections(indoc! {"
22415        [EXCERPT]
22416        [FOLDED]
22417        [EXCERPT]
22418        ˇa1
22419        b1
22420        [EXCERPT]
22421        [FOLDED]
22422        [EXCERPT]
22423        [FOLDED]
22424        "
22425    });
22426    cx.simulate_keystroke("down");
22427    cx.assert_excerpts_with_selections(indoc! {"
22428        [EXCERPT]
22429        [FOLDED]
22430        [EXCERPT]
22431        a1
22432        ˇb1
22433        [EXCERPT]
22434        [FOLDED]
22435        [EXCERPT]
22436        [FOLDED]
22437        "
22438    });
22439    cx.simulate_keystroke("down");
22440    cx.assert_excerpts_with_selections(indoc! {"
22441        [EXCERPT]
22442        [FOLDED]
22443        [EXCERPT]
22444        a1
22445        b1
22446        ˇ[EXCERPT]
22447        [FOLDED]
22448        [EXCERPT]
22449        [FOLDED]
22450        "
22451    });
22452    cx.simulate_keystroke("down");
22453    cx.assert_excerpts_with_selections(indoc! {"
22454        [EXCERPT]
22455        [FOLDED]
22456        [EXCERPT]
22457        a1
22458        b1
22459        [EXCERPT]
22460        ˇ[FOLDED]
22461        [EXCERPT]
22462        [FOLDED]
22463        "
22464    });
22465    for _ in 0..5 {
22466        cx.simulate_keystroke("down");
22467        cx.assert_excerpts_with_selections(indoc! {"
22468            [EXCERPT]
22469            [FOLDED]
22470            [EXCERPT]
22471            a1
22472            b1
22473            [EXCERPT]
22474            [FOLDED]
22475            [EXCERPT]
22476            ˇ[FOLDED]
22477            "
22478        });
22479    }
22480
22481    cx.simulate_keystroke("up");
22482    cx.assert_excerpts_with_selections(indoc! {"
22483        [EXCERPT]
22484        [FOLDED]
22485        [EXCERPT]
22486        a1
22487        b1
22488        [EXCERPT]
22489        ˇ[FOLDED]
22490        [EXCERPT]
22491        [FOLDED]
22492        "
22493    });
22494    cx.simulate_keystroke("up");
22495    cx.assert_excerpts_with_selections(indoc! {"
22496        [EXCERPT]
22497        [FOLDED]
22498        [EXCERPT]
22499        a1
22500        b1
22501        ˇ[EXCERPT]
22502        [FOLDED]
22503        [EXCERPT]
22504        [FOLDED]
22505        "
22506    });
22507    cx.simulate_keystroke("up");
22508    cx.assert_excerpts_with_selections(indoc! {"
22509        [EXCERPT]
22510        [FOLDED]
22511        [EXCERPT]
22512        a1
22513        ˇb1
22514        [EXCERPT]
22515        [FOLDED]
22516        [EXCERPT]
22517        [FOLDED]
22518        "
22519    });
22520    cx.simulate_keystroke("up");
22521    cx.assert_excerpts_with_selections(indoc! {"
22522        [EXCERPT]
22523        [FOLDED]
22524        [EXCERPT]
22525        ˇa1
22526        b1
22527        [EXCERPT]
22528        [FOLDED]
22529        [EXCERPT]
22530        [FOLDED]
22531        "
22532    });
22533    for _ in 0..5 {
22534        cx.simulate_keystroke("up");
22535        cx.assert_excerpts_with_selections(indoc! {"
22536            [EXCERPT]
22537            ˇ[FOLDED]
22538            [EXCERPT]
22539            a1
22540            b1
22541            [EXCERPT]
22542            [FOLDED]
22543            [EXCERPT]
22544            [FOLDED]
22545            "
22546        });
22547    }
22548}
22549
22550#[gpui::test]
22551async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22552    init_test(cx, |_| {});
22553
22554    // Simple insertion
22555    assert_highlighted_edits(
22556        "Hello, world!",
22557        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22558        true,
22559        cx,
22560        |highlighted_edits, cx| {
22561            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22562            assert_eq!(highlighted_edits.highlights.len(), 1);
22563            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
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    // Replacement
22573    assert_highlighted_edits(
22574        "This is a test.",
22575        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22576        false,
22577        cx,
22578        |highlighted_edits, cx| {
22579            assert_eq!(highlighted_edits.text, "That is a test.");
22580            assert_eq!(highlighted_edits.highlights.len(), 1);
22581            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22582            assert_eq!(
22583                highlighted_edits.highlights[0].1.background_color,
22584                Some(cx.theme().status().created_background)
22585            );
22586        },
22587    )
22588    .await;
22589
22590    // Multiple edits
22591    assert_highlighted_edits(
22592        "Hello, world!",
22593        vec![
22594            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22595            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22596        ],
22597        false,
22598        cx,
22599        |highlighted_edits, cx| {
22600            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22601            assert_eq!(highlighted_edits.highlights.len(), 2);
22602            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22603            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22604            assert_eq!(
22605                highlighted_edits.highlights[0].1.background_color,
22606                Some(cx.theme().status().created_background)
22607            );
22608            assert_eq!(
22609                highlighted_edits.highlights[1].1.background_color,
22610                Some(cx.theme().status().created_background)
22611            );
22612        },
22613    )
22614    .await;
22615
22616    // Multiple lines with edits
22617    assert_highlighted_edits(
22618        "First line\nSecond line\nThird line\nFourth line",
22619        vec![
22620            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22621            (
22622                Point::new(2, 0)..Point::new(2, 10),
22623                "New third line".to_string(),
22624            ),
22625            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22626        ],
22627        false,
22628        cx,
22629        |highlighted_edits, cx| {
22630            assert_eq!(
22631                highlighted_edits.text,
22632                "Second modified\nNew third line\nFourth updated line"
22633            );
22634            assert_eq!(highlighted_edits.highlights.len(), 3);
22635            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22636            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22637            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22638            for highlight in &highlighted_edits.highlights {
22639                assert_eq!(
22640                    highlight.1.background_color,
22641                    Some(cx.theme().status().created_background)
22642                );
22643            }
22644        },
22645    )
22646    .await;
22647}
22648
22649#[gpui::test]
22650async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22651    init_test(cx, |_| {});
22652
22653    // Deletion
22654    assert_highlighted_edits(
22655        "Hello, world!",
22656        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22657        true,
22658        cx,
22659        |highlighted_edits, cx| {
22660            assert_eq!(highlighted_edits.text, "Hello, world!");
22661            assert_eq!(highlighted_edits.highlights.len(), 1);
22662            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22663            assert_eq!(
22664                highlighted_edits.highlights[0].1.background_color,
22665                Some(cx.theme().status().deleted_background)
22666            );
22667        },
22668    )
22669    .await;
22670
22671    // Insertion
22672    assert_highlighted_edits(
22673        "Hello, world!",
22674        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22675        true,
22676        cx,
22677        |highlighted_edits, cx| {
22678            assert_eq!(highlighted_edits.highlights.len(), 1);
22679            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22680            assert_eq!(
22681                highlighted_edits.highlights[0].1.background_color,
22682                Some(cx.theme().status().created_background)
22683            );
22684        },
22685    )
22686    .await;
22687}
22688
22689async fn assert_highlighted_edits(
22690    text: &str,
22691    edits: Vec<(Range<Point>, String)>,
22692    include_deletions: bool,
22693    cx: &mut TestAppContext,
22694    assertion_fn: impl Fn(HighlightedText, &App),
22695) {
22696    let window = cx.add_window(|window, cx| {
22697        let buffer = MultiBuffer::build_simple(text, cx);
22698        Editor::new(EditorMode::full(), buffer, None, window, cx)
22699    });
22700    let cx = &mut VisualTestContext::from_window(*window, cx);
22701
22702    let (buffer, snapshot) = window
22703        .update(cx, |editor, _window, cx| {
22704            (
22705                editor.buffer().clone(),
22706                editor.buffer().read(cx).snapshot(cx),
22707            )
22708        })
22709        .unwrap();
22710
22711    let edits = edits
22712        .into_iter()
22713        .map(|(range, edit)| {
22714            (
22715                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22716                edit,
22717            )
22718        })
22719        .collect::<Vec<_>>();
22720
22721    let text_anchor_edits = edits
22722        .clone()
22723        .into_iter()
22724        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22725        .collect::<Vec<_>>();
22726
22727    let edit_preview = window
22728        .update(cx, |_, _window, cx| {
22729            buffer
22730                .read(cx)
22731                .as_singleton()
22732                .unwrap()
22733                .read(cx)
22734                .preview_edits(text_anchor_edits.into(), cx)
22735        })
22736        .unwrap()
22737        .await;
22738
22739    cx.update(|_window, cx| {
22740        let highlighted_edits = edit_prediction_edit_text(
22741            snapshot.as_singleton().unwrap().2,
22742            &edits,
22743            &edit_preview,
22744            include_deletions,
22745            cx,
22746        );
22747        assertion_fn(highlighted_edits, cx)
22748    });
22749}
22750
22751#[track_caller]
22752fn assert_breakpoint(
22753    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22754    path: &Arc<Path>,
22755    expected: Vec<(u32, Breakpoint)>,
22756) {
22757    if expected.is_empty() {
22758        assert!(!breakpoints.contains_key(path), "{}", path.display());
22759    } else {
22760        let mut breakpoint = breakpoints
22761            .get(path)
22762            .unwrap()
22763            .iter()
22764            .map(|breakpoint| {
22765                (
22766                    breakpoint.row,
22767                    Breakpoint {
22768                        message: breakpoint.message.clone(),
22769                        state: breakpoint.state,
22770                        condition: breakpoint.condition.clone(),
22771                        hit_condition: breakpoint.hit_condition.clone(),
22772                    },
22773                )
22774            })
22775            .collect::<Vec<_>>();
22776
22777        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22778
22779        assert_eq!(expected, breakpoint);
22780    }
22781}
22782
22783fn add_log_breakpoint_at_cursor(
22784    editor: &mut Editor,
22785    log_message: &str,
22786    window: &mut Window,
22787    cx: &mut Context<Editor>,
22788) {
22789    let (anchor, bp) = editor
22790        .breakpoints_at_cursors(window, cx)
22791        .first()
22792        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22793        .unwrap_or_else(|| {
22794            let snapshot = editor.snapshot(window, cx);
22795            let cursor_position: Point =
22796                editor.selections.newest(&snapshot.display_snapshot).head();
22797
22798            let breakpoint_position = snapshot
22799                .buffer_snapshot()
22800                .anchor_before(Point::new(cursor_position.row, 0));
22801
22802            (breakpoint_position, Breakpoint::new_log(log_message))
22803        });
22804
22805    editor.edit_breakpoint_at_anchor(
22806        anchor,
22807        bp,
22808        BreakpointEditAction::EditLogMessage(log_message.into()),
22809        cx,
22810    );
22811}
22812
22813#[gpui::test]
22814async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22815    init_test(cx, |_| {});
22816
22817    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22818    let fs = FakeFs::new(cx.executor());
22819    fs.insert_tree(
22820        path!("/a"),
22821        json!({
22822            "main.rs": sample_text,
22823        }),
22824    )
22825    .await;
22826    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22827    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22828    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22829
22830    let fs = FakeFs::new(cx.executor());
22831    fs.insert_tree(
22832        path!("/a"),
22833        json!({
22834            "main.rs": sample_text,
22835        }),
22836    )
22837    .await;
22838    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22839    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22840    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22841    let worktree_id = workspace
22842        .update(cx, |workspace, _window, cx| {
22843            workspace.project().update(cx, |project, cx| {
22844                project.worktrees(cx).next().unwrap().read(cx).id()
22845            })
22846        })
22847        .unwrap();
22848
22849    let buffer = project
22850        .update(cx, |project, cx| {
22851            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22852        })
22853        .await
22854        .unwrap();
22855
22856    let (editor, cx) = cx.add_window_view(|window, cx| {
22857        Editor::new(
22858            EditorMode::full(),
22859            MultiBuffer::build_from_buffer(buffer, cx),
22860            Some(project.clone()),
22861            window,
22862            cx,
22863        )
22864    });
22865
22866    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22867    let abs_path = project.read_with(cx, |project, cx| {
22868        project
22869            .absolute_path(&project_path, cx)
22870            .map(Arc::from)
22871            .unwrap()
22872    });
22873
22874    // assert we can add breakpoint on the first line
22875    editor.update_in(cx, |editor, window, cx| {
22876        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22877        editor.move_to_end(&MoveToEnd, window, cx);
22878        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22879    });
22880
22881    let breakpoints = editor.update(cx, |editor, cx| {
22882        editor
22883            .breakpoint_store()
22884            .as_ref()
22885            .unwrap()
22886            .read(cx)
22887            .all_source_breakpoints(cx)
22888    });
22889
22890    assert_eq!(1, breakpoints.len());
22891    assert_breakpoint(
22892        &breakpoints,
22893        &abs_path,
22894        vec![
22895            (0, Breakpoint::new_standard()),
22896            (3, Breakpoint::new_standard()),
22897        ],
22898    );
22899
22900    editor.update_in(cx, |editor, window, cx| {
22901        editor.move_to_beginning(&MoveToBeginning, window, cx);
22902        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22903    });
22904
22905    let breakpoints = editor.update(cx, |editor, cx| {
22906        editor
22907            .breakpoint_store()
22908            .as_ref()
22909            .unwrap()
22910            .read(cx)
22911            .all_source_breakpoints(cx)
22912    });
22913
22914    assert_eq!(1, breakpoints.len());
22915    assert_breakpoint(
22916        &breakpoints,
22917        &abs_path,
22918        vec![(3, Breakpoint::new_standard())],
22919    );
22920
22921    editor.update_in(cx, |editor, window, cx| {
22922        editor.move_to_end(&MoveToEnd, window, cx);
22923        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22924    });
22925
22926    let breakpoints = editor.update(cx, |editor, cx| {
22927        editor
22928            .breakpoint_store()
22929            .as_ref()
22930            .unwrap()
22931            .read(cx)
22932            .all_source_breakpoints(cx)
22933    });
22934
22935    assert_eq!(0, breakpoints.len());
22936    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22937}
22938
22939#[gpui::test]
22940async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22941    init_test(cx, |_| {});
22942
22943    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22944
22945    let fs = FakeFs::new(cx.executor());
22946    fs.insert_tree(
22947        path!("/a"),
22948        json!({
22949            "main.rs": sample_text,
22950        }),
22951    )
22952    .await;
22953    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22954    let (workspace, cx) =
22955        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22956
22957    let worktree_id = workspace.update(cx, |workspace, cx| {
22958        workspace.project().update(cx, |project, cx| {
22959            project.worktrees(cx).next().unwrap().read(cx).id()
22960        })
22961    });
22962
22963    let buffer = project
22964        .update(cx, |project, cx| {
22965            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22966        })
22967        .await
22968        .unwrap();
22969
22970    let (editor, cx) = cx.add_window_view(|window, cx| {
22971        Editor::new(
22972            EditorMode::full(),
22973            MultiBuffer::build_from_buffer(buffer, cx),
22974            Some(project.clone()),
22975            window,
22976            cx,
22977        )
22978    });
22979
22980    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22981    let abs_path = project.read_with(cx, |project, cx| {
22982        project
22983            .absolute_path(&project_path, cx)
22984            .map(Arc::from)
22985            .unwrap()
22986    });
22987
22988    editor.update_in(cx, |editor, window, cx| {
22989        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22990    });
22991
22992    let breakpoints = editor.update(cx, |editor, cx| {
22993        editor
22994            .breakpoint_store()
22995            .as_ref()
22996            .unwrap()
22997            .read(cx)
22998            .all_source_breakpoints(cx)
22999    });
23000
23001    assert_breakpoint(
23002        &breakpoints,
23003        &abs_path,
23004        vec![(0, Breakpoint::new_log("hello world"))],
23005    );
23006
23007    // Removing a log message from a log breakpoint should remove it
23008    editor.update_in(cx, |editor, window, cx| {
23009        add_log_breakpoint_at_cursor(editor, "", window, cx);
23010    });
23011
23012    let breakpoints = editor.update(cx, |editor, cx| {
23013        editor
23014            .breakpoint_store()
23015            .as_ref()
23016            .unwrap()
23017            .read(cx)
23018            .all_source_breakpoints(cx)
23019    });
23020
23021    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23022
23023    editor.update_in(cx, |editor, window, cx| {
23024        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23025        editor.move_to_end(&MoveToEnd, window, cx);
23026        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23027        // Not adding a log message to a standard breakpoint shouldn't remove it
23028        add_log_breakpoint_at_cursor(editor, "", window, cx);
23029    });
23030
23031    let breakpoints = editor.update(cx, |editor, cx| {
23032        editor
23033            .breakpoint_store()
23034            .as_ref()
23035            .unwrap()
23036            .read(cx)
23037            .all_source_breakpoints(cx)
23038    });
23039
23040    assert_breakpoint(
23041        &breakpoints,
23042        &abs_path,
23043        vec![
23044            (0, Breakpoint::new_standard()),
23045            (3, Breakpoint::new_standard()),
23046        ],
23047    );
23048
23049    editor.update_in(cx, |editor, window, cx| {
23050        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23051    });
23052
23053    let breakpoints = editor.update(cx, |editor, cx| {
23054        editor
23055            .breakpoint_store()
23056            .as_ref()
23057            .unwrap()
23058            .read(cx)
23059            .all_source_breakpoints(cx)
23060    });
23061
23062    assert_breakpoint(
23063        &breakpoints,
23064        &abs_path,
23065        vec![
23066            (0, Breakpoint::new_standard()),
23067            (3, Breakpoint::new_log("hello world")),
23068        ],
23069    );
23070
23071    editor.update_in(cx, |editor, window, cx| {
23072        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23073    });
23074
23075    let breakpoints = editor.update(cx, |editor, cx| {
23076        editor
23077            .breakpoint_store()
23078            .as_ref()
23079            .unwrap()
23080            .read(cx)
23081            .all_source_breakpoints(cx)
23082    });
23083
23084    assert_breakpoint(
23085        &breakpoints,
23086        &abs_path,
23087        vec![
23088            (0, Breakpoint::new_standard()),
23089            (3, Breakpoint::new_log("hello Earth!!")),
23090        ],
23091    );
23092}
23093
23094/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23095/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23096/// or when breakpoints were placed out of order. This tests for a regression too
23097#[gpui::test]
23098async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23099    init_test(cx, |_| {});
23100
23101    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23102    let fs = FakeFs::new(cx.executor());
23103    fs.insert_tree(
23104        path!("/a"),
23105        json!({
23106            "main.rs": sample_text,
23107        }),
23108    )
23109    .await;
23110    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23111    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23112    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23113
23114    let fs = FakeFs::new(cx.executor());
23115    fs.insert_tree(
23116        path!("/a"),
23117        json!({
23118            "main.rs": sample_text,
23119        }),
23120    )
23121    .await;
23122    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23123    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23124    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23125    let worktree_id = workspace
23126        .update(cx, |workspace, _window, cx| {
23127            workspace.project().update(cx, |project, cx| {
23128                project.worktrees(cx).next().unwrap().read(cx).id()
23129            })
23130        })
23131        .unwrap();
23132
23133    let buffer = project
23134        .update(cx, |project, cx| {
23135            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23136        })
23137        .await
23138        .unwrap();
23139
23140    let (editor, cx) = cx.add_window_view(|window, cx| {
23141        Editor::new(
23142            EditorMode::full(),
23143            MultiBuffer::build_from_buffer(buffer, cx),
23144            Some(project.clone()),
23145            window,
23146            cx,
23147        )
23148    });
23149
23150    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23151    let abs_path = project.read_with(cx, |project, cx| {
23152        project
23153            .absolute_path(&project_path, cx)
23154            .map(Arc::from)
23155            .unwrap()
23156    });
23157
23158    // assert we can add breakpoint on the first line
23159    editor.update_in(cx, |editor, window, cx| {
23160        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23161        editor.move_to_end(&MoveToEnd, window, cx);
23162        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23163        editor.move_up(&MoveUp, window, cx);
23164        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23165    });
23166
23167    let breakpoints = editor.update(cx, |editor, cx| {
23168        editor
23169            .breakpoint_store()
23170            .as_ref()
23171            .unwrap()
23172            .read(cx)
23173            .all_source_breakpoints(cx)
23174    });
23175
23176    assert_eq!(1, breakpoints.len());
23177    assert_breakpoint(
23178        &breakpoints,
23179        &abs_path,
23180        vec![
23181            (0, Breakpoint::new_standard()),
23182            (2, Breakpoint::new_standard()),
23183            (3, Breakpoint::new_standard()),
23184        ],
23185    );
23186
23187    editor.update_in(cx, |editor, window, cx| {
23188        editor.move_to_beginning(&MoveToBeginning, window, cx);
23189        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23190        editor.move_to_end(&MoveToEnd, window, cx);
23191        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23192        // Disabling a breakpoint that doesn't exist should do nothing
23193        editor.move_up(&MoveUp, window, cx);
23194        editor.move_up(&MoveUp, window, cx);
23195        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23196    });
23197
23198    let breakpoints = editor.update(cx, |editor, cx| {
23199        editor
23200            .breakpoint_store()
23201            .as_ref()
23202            .unwrap()
23203            .read(cx)
23204            .all_source_breakpoints(cx)
23205    });
23206
23207    let disable_breakpoint = {
23208        let mut bp = Breakpoint::new_standard();
23209        bp.state = BreakpointState::Disabled;
23210        bp
23211    };
23212
23213    assert_eq!(1, breakpoints.len());
23214    assert_breakpoint(
23215        &breakpoints,
23216        &abs_path,
23217        vec![
23218            (0, disable_breakpoint.clone()),
23219            (2, Breakpoint::new_standard()),
23220            (3, disable_breakpoint.clone()),
23221        ],
23222    );
23223
23224    editor.update_in(cx, |editor, window, cx| {
23225        editor.move_to_beginning(&MoveToBeginning, window, cx);
23226        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23227        editor.move_to_end(&MoveToEnd, window, cx);
23228        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23229        editor.move_up(&MoveUp, window, cx);
23230        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23231    });
23232
23233    let breakpoints = editor.update(cx, |editor, cx| {
23234        editor
23235            .breakpoint_store()
23236            .as_ref()
23237            .unwrap()
23238            .read(cx)
23239            .all_source_breakpoints(cx)
23240    });
23241
23242    assert_eq!(1, breakpoints.len());
23243    assert_breakpoint(
23244        &breakpoints,
23245        &abs_path,
23246        vec![
23247            (0, Breakpoint::new_standard()),
23248            (2, disable_breakpoint),
23249            (3, Breakpoint::new_standard()),
23250        ],
23251    );
23252}
23253
23254#[gpui::test]
23255async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23256    init_test(cx, |_| {});
23257    let capabilities = lsp::ServerCapabilities {
23258        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23259            prepare_provider: Some(true),
23260            work_done_progress_options: Default::default(),
23261        })),
23262        ..Default::default()
23263    };
23264    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23265
23266    cx.set_state(indoc! {"
23267        struct Fˇoo {}
23268    "});
23269
23270    cx.update_editor(|editor, _, cx| {
23271        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23272        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23273        editor.highlight_background::<DocumentHighlightRead>(
23274            &[highlight_range],
23275            |theme| theme.colors().editor_document_highlight_read_background,
23276            cx,
23277        );
23278    });
23279
23280    let mut prepare_rename_handler = cx
23281        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23282            move |_, _, _| async move {
23283                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23284                    start: lsp::Position {
23285                        line: 0,
23286                        character: 7,
23287                    },
23288                    end: lsp::Position {
23289                        line: 0,
23290                        character: 10,
23291                    },
23292                })))
23293            },
23294        );
23295    let prepare_rename_task = cx
23296        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23297        .expect("Prepare rename was not started");
23298    prepare_rename_handler.next().await.unwrap();
23299    prepare_rename_task.await.expect("Prepare rename failed");
23300
23301    let mut rename_handler =
23302        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23303            let edit = lsp::TextEdit {
23304                range: lsp::Range {
23305                    start: lsp::Position {
23306                        line: 0,
23307                        character: 7,
23308                    },
23309                    end: lsp::Position {
23310                        line: 0,
23311                        character: 10,
23312                    },
23313                },
23314                new_text: "FooRenamed".to_string(),
23315            };
23316            Ok(Some(lsp::WorkspaceEdit::new(
23317                // Specify the same edit twice
23318                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23319            )))
23320        });
23321    let rename_task = cx
23322        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23323        .expect("Confirm rename was not started");
23324    rename_handler.next().await.unwrap();
23325    rename_task.await.expect("Confirm rename failed");
23326    cx.run_until_parked();
23327
23328    // Despite two edits, only one is actually applied as those are identical
23329    cx.assert_editor_state(indoc! {"
23330        struct FooRenamedˇ {}
23331    "});
23332}
23333
23334#[gpui::test]
23335async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23336    init_test(cx, |_| {});
23337    // These capabilities indicate that the server does not support prepare rename.
23338    let capabilities = lsp::ServerCapabilities {
23339        rename_provider: Some(lsp::OneOf::Left(true)),
23340        ..Default::default()
23341    };
23342    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23343
23344    cx.set_state(indoc! {"
23345        struct Fˇoo {}
23346    "});
23347
23348    cx.update_editor(|editor, _window, cx| {
23349        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23350        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23351        editor.highlight_background::<DocumentHighlightRead>(
23352            &[highlight_range],
23353            |theme| theme.colors().editor_document_highlight_read_background,
23354            cx,
23355        );
23356    });
23357
23358    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23359        .expect("Prepare rename was not started")
23360        .await
23361        .expect("Prepare rename failed");
23362
23363    let mut rename_handler =
23364        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23365            let edit = lsp::TextEdit {
23366                range: lsp::Range {
23367                    start: lsp::Position {
23368                        line: 0,
23369                        character: 7,
23370                    },
23371                    end: lsp::Position {
23372                        line: 0,
23373                        character: 10,
23374                    },
23375                },
23376                new_text: "FooRenamed".to_string(),
23377            };
23378            Ok(Some(lsp::WorkspaceEdit::new(
23379                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23380            )))
23381        });
23382    let rename_task = cx
23383        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23384        .expect("Confirm rename was not started");
23385    rename_handler.next().await.unwrap();
23386    rename_task.await.expect("Confirm rename failed");
23387    cx.run_until_parked();
23388
23389    // Correct range is renamed, as `surrounding_word` is used to find it.
23390    cx.assert_editor_state(indoc! {"
23391        struct FooRenamedˇ {}
23392    "});
23393}
23394
23395#[gpui::test]
23396async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23397    init_test(cx, |_| {});
23398    let mut cx = EditorTestContext::new(cx).await;
23399
23400    let language = Arc::new(
23401        Language::new(
23402            LanguageConfig::default(),
23403            Some(tree_sitter_html::LANGUAGE.into()),
23404        )
23405        .with_brackets_query(
23406            r#"
23407            ("<" @open "/>" @close)
23408            ("</" @open ">" @close)
23409            ("<" @open ">" @close)
23410            ("\"" @open "\"" @close)
23411            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23412        "#,
23413        )
23414        .unwrap(),
23415    );
23416    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23417
23418    cx.set_state(indoc! {"
23419        <span>ˇ</span>
23420    "});
23421    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23422    cx.assert_editor_state(indoc! {"
23423        <span>
23424        ˇ
23425        </span>
23426    "});
23427
23428    cx.set_state(indoc! {"
23429        <span><span></span>ˇ</span>
23430    "});
23431    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23432    cx.assert_editor_state(indoc! {"
23433        <span><span></span>
23434        ˇ</span>
23435    "});
23436
23437    cx.set_state(indoc! {"
23438        <span>ˇ
23439        </span>
23440    "});
23441    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23442    cx.assert_editor_state(indoc! {"
23443        <span>
23444        ˇ
23445        </span>
23446    "});
23447}
23448
23449#[gpui::test(iterations = 10)]
23450async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23451    init_test(cx, |_| {});
23452
23453    let fs = FakeFs::new(cx.executor());
23454    fs.insert_tree(
23455        path!("/dir"),
23456        json!({
23457            "a.ts": "a",
23458        }),
23459    )
23460    .await;
23461
23462    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23463    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23464    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23465
23466    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23467    language_registry.add(Arc::new(Language::new(
23468        LanguageConfig {
23469            name: "TypeScript".into(),
23470            matcher: LanguageMatcher {
23471                path_suffixes: vec!["ts".to_string()],
23472                ..Default::default()
23473            },
23474            ..Default::default()
23475        },
23476        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23477    )));
23478    let mut fake_language_servers = language_registry.register_fake_lsp(
23479        "TypeScript",
23480        FakeLspAdapter {
23481            capabilities: lsp::ServerCapabilities {
23482                code_lens_provider: Some(lsp::CodeLensOptions {
23483                    resolve_provider: Some(true),
23484                }),
23485                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23486                    commands: vec!["_the/command".to_string()],
23487                    ..lsp::ExecuteCommandOptions::default()
23488                }),
23489                ..lsp::ServerCapabilities::default()
23490            },
23491            ..FakeLspAdapter::default()
23492        },
23493    );
23494
23495    let editor = workspace
23496        .update(cx, |workspace, window, cx| {
23497            workspace.open_abs_path(
23498                PathBuf::from(path!("/dir/a.ts")),
23499                OpenOptions::default(),
23500                window,
23501                cx,
23502            )
23503        })
23504        .unwrap()
23505        .await
23506        .unwrap()
23507        .downcast::<Editor>()
23508        .unwrap();
23509    cx.executor().run_until_parked();
23510
23511    let fake_server = fake_language_servers.next().await.unwrap();
23512
23513    let buffer = editor.update(cx, |editor, cx| {
23514        editor
23515            .buffer()
23516            .read(cx)
23517            .as_singleton()
23518            .expect("have opened a single file by path")
23519    });
23520
23521    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23522    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23523    drop(buffer_snapshot);
23524    let actions = cx
23525        .update_window(*workspace, |_, window, cx| {
23526            project.code_actions(&buffer, anchor..anchor, window, cx)
23527        })
23528        .unwrap();
23529
23530    fake_server
23531        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23532            Ok(Some(vec![
23533                lsp::CodeLens {
23534                    range: lsp::Range::default(),
23535                    command: Some(lsp::Command {
23536                        title: "Code lens command".to_owned(),
23537                        command: "_the/command".to_owned(),
23538                        arguments: None,
23539                    }),
23540                    data: None,
23541                },
23542                lsp::CodeLens {
23543                    range: lsp::Range::default(),
23544                    command: Some(lsp::Command {
23545                        title: "Command not in capabilities".to_owned(),
23546                        command: "not in capabilities".to_owned(),
23547                        arguments: None,
23548                    }),
23549                    data: None,
23550                },
23551                lsp::CodeLens {
23552                    range: lsp::Range {
23553                        start: lsp::Position {
23554                            line: 1,
23555                            character: 1,
23556                        },
23557                        end: lsp::Position {
23558                            line: 1,
23559                            character: 1,
23560                        },
23561                    },
23562                    command: Some(lsp::Command {
23563                        title: "Command not in range".to_owned(),
23564                        command: "_the/command".to_owned(),
23565                        arguments: None,
23566                    }),
23567                    data: None,
23568                },
23569            ]))
23570        })
23571        .next()
23572        .await;
23573
23574    let actions = actions.await.unwrap();
23575    assert_eq!(
23576        actions.len(),
23577        1,
23578        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23579    );
23580    let action = actions[0].clone();
23581    let apply = project.update(cx, |project, cx| {
23582        project.apply_code_action(buffer.clone(), action, true, cx)
23583    });
23584
23585    // Resolving the code action does not populate its edits. In absence of
23586    // edits, we must execute the given command.
23587    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23588        |mut lens, _| async move {
23589            let lens_command = lens.command.as_mut().expect("should have a command");
23590            assert_eq!(lens_command.title, "Code lens command");
23591            lens_command.arguments = Some(vec![json!("the-argument")]);
23592            Ok(lens)
23593        },
23594    );
23595
23596    // While executing the command, the language server sends the editor
23597    // a `workspaceEdit` request.
23598    fake_server
23599        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23600            let fake = fake_server.clone();
23601            move |params, _| {
23602                assert_eq!(params.command, "_the/command");
23603                let fake = fake.clone();
23604                async move {
23605                    fake.server
23606                        .request::<lsp::request::ApplyWorkspaceEdit>(
23607                            lsp::ApplyWorkspaceEditParams {
23608                                label: None,
23609                                edit: lsp::WorkspaceEdit {
23610                                    changes: Some(
23611                                        [(
23612                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23613                                            vec![lsp::TextEdit {
23614                                                range: lsp::Range::new(
23615                                                    lsp::Position::new(0, 0),
23616                                                    lsp::Position::new(0, 0),
23617                                                ),
23618                                                new_text: "X".into(),
23619                                            }],
23620                                        )]
23621                                        .into_iter()
23622                                        .collect(),
23623                                    ),
23624                                    ..lsp::WorkspaceEdit::default()
23625                                },
23626                            },
23627                        )
23628                        .await
23629                        .into_response()
23630                        .unwrap();
23631                    Ok(Some(json!(null)))
23632                }
23633            }
23634        })
23635        .next()
23636        .await;
23637
23638    // Applying the code lens command returns a project transaction containing the edits
23639    // sent by the language server in its `workspaceEdit` request.
23640    let transaction = apply.await.unwrap();
23641    assert!(transaction.0.contains_key(&buffer));
23642    buffer.update(cx, |buffer, cx| {
23643        assert_eq!(buffer.text(), "Xa");
23644        buffer.undo(cx);
23645        assert_eq!(buffer.text(), "a");
23646    });
23647
23648    let actions_after_edits = cx
23649        .update_window(*workspace, |_, window, cx| {
23650            project.code_actions(&buffer, anchor..anchor, window, cx)
23651        })
23652        .unwrap()
23653        .await
23654        .unwrap();
23655    assert_eq!(
23656        actions, actions_after_edits,
23657        "For the same selection, same code lens actions should be returned"
23658    );
23659
23660    let _responses =
23661        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23662            panic!("No more code lens requests are expected");
23663        });
23664    editor.update_in(cx, |editor, window, cx| {
23665        editor.select_all(&SelectAll, window, cx);
23666    });
23667    cx.executor().run_until_parked();
23668    let new_actions = cx
23669        .update_window(*workspace, |_, window, cx| {
23670            project.code_actions(&buffer, anchor..anchor, window, cx)
23671        })
23672        .unwrap()
23673        .await
23674        .unwrap();
23675    assert_eq!(
23676        actions, new_actions,
23677        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23678    );
23679}
23680
23681#[gpui::test]
23682async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23683    init_test(cx, |_| {});
23684
23685    let fs = FakeFs::new(cx.executor());
23686    let main_text = r#"fn main() {
23687println!("1");
23688println!("2");
23689println!("3");
23690println!("4");
23691println!("5");
23692}"#;
23693    let lib_text = "mod foo {}";
23694    fs.insert_tree(
23695        path!("/a"),
23696        json!({
23697            "lib.rs": lib_text,
23698            "main.rs": main_text,
23699        }),
23700    )
23701    .await;
23702
23703    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23704    let (workspace, cx) =
23705        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23706    let worktree_id = workspace.update(cx, |workspace, cx| {
23707        workspace.project().update(cx, |project, cx| {
23708            project.worktrees(cx).next().unwrap().read(cx).id()
23709        })
23710    });
23711
23712    let expected_ranges = vec![
23713        Point::new(0, 0)..Point::new(0, 0),
23714        Point::new(1, 0)..Point::new(1, 1),
23715        Point::new(2, 0)..Point::new(2, 2),
23716        Point::new(3, 0)..Point::new(3, 3),
23717    ];
23718
23719    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23720    let editor_1 = workspace
23721        .update_in(cx, |workspace, window, cx| {
23722            workspace.open_path(
23723                (worktree_id, rel_path("main.rs")),
23724                Some(pane_1.downgrade()),
23725                true,
23726                window,
23727                cx,
23728            )
23729        })
23730        .unwrap()
23731        .await
23732        .downcast::<Editor>()
23733        .unwrap();
23734    pane_1.update(cx, |pane, cx| {
23735        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23736        open_editor.update(cx, |editor, cx| {
23737            assert_eq!(
23738                editor.display_text(cx),
23739                main_text,
23740                "Original main.rs text on initial open",
23741            );
23742            assert_eq!(
23743                editor
23744                    .selections
23745                    .all::<Point>(&editor.display_snapshot(cx))
23746                    .into_iter()
23747                    .map(|s| s.range())
23748                    .collect::<Vec<_>>(),
23749                vec![Point::zero()..Point::zero()],
23750                "Default selections on initial open",
23751            );
23752        })
23753    });
23754    editor_1.update_in(cx, |editor, window, cx| {
23755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23756            s.select_ranges(expected_ranges.clone());
23757        });
23758    });
23759
23760    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23761        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23762    });
23763    let editor_2 = workspace
23764        .update_in(cx, |workspace, window, cx| {
23765            workspace.open_path(
23766                (worktree_id, rel_path("main.rs")),
23767                Some(pane_2.downgrade()),
23768                true,
23769                window,
23770                cx,
23771            )
23772        })
23773        .unwrap()
23774        .await
23775        .downcast::<Editor>()
23776        .unwrap();
23777    pane_2.update(cx, |pane, cx| {
23778        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23779        open_editor.update(cx, |editor, cx| {
23780            assert_eq!(
23781                editor.display_text(cx),
23782                main_text,
23783                "Original main.rs text on initial open in another panel",
23784            );
23785            assert_eq!(
23786                editor
23787                    .selections
23788                    .all::<Point>(&editor.display_snapshot(cx))
23789                    .into_iter()
23790                    .map(|s| s.range())
23791                    .collect::<Vec<_>>(),
23792                vec![Point::zero()..Point::zero()],
23793                "Default selections on initial open in another panel",
23794            );
23795        })
23796    });
23797
23798    editor_2.update_in(cx, |editor, window, cx| {
23799        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23800    });
23801
23802    let _other_editor_1 = workspace
23803        .update_in(cx, |workspace, window, cx| {
23804            workspace.open_path(
23805                (worktree_id, rel_path("lib.rs")),
23806                Some(pane_1.downgrade()),
23807                true,
23808                window,
23809                cx,
23810            )
23811        })
23812        .unwrap()
23813        .await
23814        .downcast::<Editor>()
23815        .unwrap();
23816    pane_1
23817        .update_in(cx, |pane, window, cx| {
23818            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23819        })
23820        .await
23821        .unwrap();
23822    drop(editor_1);
23823    pane_1.update(cx, |pane, cx| {
23824        pane.active_item()
23825            .unwrap()
23826            .downcast::<Editor>()
23827            .unwrap()
23828            .update(cx, |editor, cx| {
23829                assert_eq!(
23830                    editor.display_text(cx),
23831                    lib_text,
23832                    "Other file should be open and active",
23833                );
23834            });
23835        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23836    });
23837
23838    let _other_editor_2 = workspace
23839        .update_in(cx, |workspace, window, cx| {
23840            workspace.open_path(
23841                (worktree_id, rel_path("lib.rs")),
23842                Some(pane_2.downgrade()),
23843                true,
23844                window,
23845                cx,
23846            )
23847        })
23848        .unwrap()
23849        .await
23850        .downcast::<Editor>()
23851        .unwrap();
23852    pane_2
23853        .update_in(cx, |pane, window, cx| {
23854            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23855        })
23856        .await
23857        .unwrap();
23858    drop(editor_2);
23859    pane_2.update(cx, |pane, cx| {
23860        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23861        open_editor.update(cx, |editor, cx| {
23862            assert_eq!(
23863                editor.display_text(cx),
23864                lib_text,
23865                "Other file should be open and active in another panel too",
23866            );
23867        });
23868        assert_eq!(
23869            pane.items().count(),
23870            1,
23871            "No other editors should be open in another pane",
23872        );
23873    });
23874
23875    let _editor_1_reopened = workspace
23876        .update_in(cx, |workspace, window, cx| {
23877            workspace.open_path(
23878                (worktree_id, rel_path("main.rs")),
23879                Some(pane_1.downgrade()),
23880                true,
23881                window,
23882                cx,
23883            )
23884        })
23885        .unwrap()
23886        .await
23887        .downcast::<Editor>()
23888        .unwrap();
23889    let _editor_2_reopened = workspace
23890        .update_in(cx, |workspace, window, cx| {
23891            workspace.open_path(
23892                (worktree_id, rel_path("main.rs")),
23893                Some(pane_2.downgrade()),
23894                true,
23895                window,
23896                cx,
23897            )
23898        })
23899        .unwrap()
23900        .await
23901        .downcast::<Editor>()
23902        .unwrap();
23903    pane_1.update(cx, |pane, cx| {
23904        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23905        open_editor.update(cx, |editor, cx| {
23906            assert_eq!(
23907                editor.display_text(cx),
23908                main_text,
23909                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23910            );
23911            assert_eq!(
23912                editor
23913                    .selections
23914                    .all::<Point>(&editor.display_snapshot(cx))
23915                    .into_iter()
23916                    .map(|s| s.range())
23917                    .collect::<Vec<_>>(),
23918                expected_ranges,
23919                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23920            );
23921        })
23922    });
23923    pane_2.update(cx, |pane, cx| {
23924        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23925        open_editor.update(cx, |editor, cx| {
23926            assert_eq!(
23927                editor.display_text(cx),
23928                r#"fn main() {
23929⋯rintln!("1");
23930⋯intln!("2");
23931⋯ntln!("3");
23932println!("4");
23933println!("5");
23934}"#,
23935                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23936            );
23937            assert_eq!(
23938                editor
23939                    .selections
23940                    .all::<Point>(&editor.display_snapshot(cx))
23941                    .into_iter()
23942                    .map(|s| s.range())
23943                    .collect::<Vec<_>>(),
23944                vec![Point::zero()..Point::zero()],
23945                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23946            );
23947        })
23948    });
23949}
23950
23951#[gpui::test]
23952async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23953    init_test(cx, |_| {});
23954
23955    let fs = FakeFs::new(cx.executor());
23956    let main_text = r#"fn main() {
23957println!("1");
23958println!("2");
23959println!("3");
23960println!("4");
23961println!("5");
23962}"#;
23963    let lib_text = "mod foo {}";
23964    fs.insert_tree(
23965        path!("/a"),
23966        json!({
23967            "lib.rs": lib_text,
23968            "main.rs": main_text,
23969        }),
23970    )
23971    .await;
23972
23973    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23974    let (workspace, cx) =
23975        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23976    let worktree_id = workspace.update(cx, |workspace, cx| {
23977        workspace.project().update(cx, |project, cx| {
23978            project.worktrees(cx).next().unwrap().read(cx).id()
23979        })
23980    });
23981
23982    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23983    let editor = workspace
23984        .update_in(cx, |workspace, window, cx| {
23985            workspace.open_path(
23986                (worktree_id, rel_path("main.rs")),
23987                Some(pane.downgrade()),
23988                true,
23989                window,
23990                cx,
23991            )
23992        })
23993        .unwrap()
23994        .await
23995        .downcast::<Editor>()
23996        .unwrap();
23997    pane.update(cx, |pane, cx| {
23998        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23999        open_editor.update(cx, |editor, cx| {
24000            assert_eq!(
24001                editor.display_text(cx),
24002                main_text,
24003                "Original main.rs text on initial open",
24004            );
24005        })
24006    });
24007    editor.update_in(cx, |editor, window, cx| {
24008        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24009    });
24010
24011    cx.update_global(|store: &mut SettingsStore, cx| {
24012        store.update_user_settings(cx, |s| {
24013            s.workspace.restore_on_file_reopen = Some(false);
24014        });
24015    });
24016    editor.update_in(cx, |editor, window, cx| {
24017        editor.fold_ranges(
24018            vec![
24019                Point::new(1, 0)..Point::new(1, 1),
24020                Point::new(2, 0)..Point::new(2, 2),
24021                Point::new(3, 0)..Point::new(3, 3),
24022            ],
24023            false,
24024            window,
24025            cx,
24026        );
24027    });
24028    pane.update_in(cx, |pane, window, cx| {
24029        pane.close_all_items(&CloseAllItems::default(), window, cx)
24030    })
24031    .await
24032    .unwrap();
24033    pane.update(cx, |pane, _| {
24034        assert!(pane.active_item().is_none());
24035    });
24036    cx.update_global(|store: &mut SettingsStore, cx| {
24037        store.update_user_settings(cx, |s| {
24038            s.workspace.restore_on_file_reopen = Some(true);
24039        });
24040    });
24041
24042    let _editor_reopened = workspace
24043        .update_in(cx, |workspace, window, cx| {
24044            workspace.open_path(
24045                (worktree_id, rel_path("main.rs")),
24046                Some(pane.downgrade()),
24047                true,
24048                window,
24049                cx,
24050            )
24051        })
24052        .unwrap()
24053        .await
24054        .downcast::<Editor>()
24055        .unwrap();
24056    pane.update(cx, |pane, cx| {
24057        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24058        open_editor.update(cx, |editor, cx| {
24059            assert_eq!(
24060                editor.display_text(cx),
24061                main_text,
24062                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24063            );
24064        })
24065    });
24066}
24067
24068#[gpui::test]
24069async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24070    struct EmptyModalView {
24071        focus_handle: gpui::FocusHandle,
24072    }
24073    impl EventEmitter<DismissEvent> for EmptyModalView {}
24074    impl Render for EmptyModalView {
24075        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24076            div()
24077        }
24078    }
24079    impl Focusable for EmptyModalView {
24080        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24081            self.focus_handle.clone()
24082        }
24083    }
24084    impl workspace::ModalView for EmptyModalView {}
24085    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24086        EmptyModalView {
24087            focus_handle: cx.focus_handle(),
24088        }
24089    }
24090
24091    init_test(cx, |_| {});
24092
24093    let fs = FakeFs::new(cx.executor());
24094    let project = Project::test(fs, [], cx).await;
24095    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24096    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24097    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24098    let editor = cx.new_window_entity(|window, cx| {
24099        Editor::new(
24100            EditorMode::full(),
24101            buffer,
24102            Some(project.clone()),
24103            window,
24104            cx,
24105        )
24106    });
24107    workspace
24108        .update(cx, |workspace, window, cx| {
24109            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24110        })
24111        .unwrap();
24112    editor.update_in(cx, |editor, window, cx| {
24113        editor.open_context_menu(&OpenContextMenu, window, cx);
24114        assert!(editor.mouse_context_menu.is_some());
24115    });
24116    workspace
24117        .update(cx, |workspace, window, cx| {
24118            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24119        })
24120        .unwrap();
24121    cx.read(|cx| {
24122        assert!(editor.read(cx).mouse_context_menu.is_none());
24123    });
24124}
24125
24126fn set_linked_edit_ranges(
24127    opening: (Point, Point),
24128    closing: (Point, Point),
24129    editor: &mut Editor,
24130    cx: &mut Context<Editor>,
24131) {
24132    let Some((buffer, _)) = editor
24133        .buffer
24134        .read(cx)
24135        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24136    else {
24137        panic!("Failed to get buffer for selection position");
24138    };
24139    let buffer = buffer.read(cx);
24140    let buffer_id = buffer.remote_id();
24141    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24142    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24143    let mut linked_ranges = HashMap::default();
24144    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24145    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24146}
24147
24148#[gpui::test]
24149async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24150    init_test(cx, |_| {});
24151
24152    let fs = FakeFs::new(cx.executor());
24153    fs.insert_file(path!("/file.html"), Default::default())
24154        .await;
24155
24156    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24157
24158    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24159    let html_language = Arc::new(Language::new(
24160        LanguageConfig {
24161            name: "HTML".into(),
24162            matcher: LanguageMatcher {
24163                path_suffixes: vec!["html".to_string()],
24164                ..LanguageMatcher::default()
24165            },
24166            brackets: BracketPairConfig {
24167                pairs: vec![BracketPair {
24168                    start: "<".into(),
24169                    end: ">".into(),
24170                    close: true,
24171                    ..Default::default()
24172                }],
24173                ..Default::default()
24174            },
24175            ..Default::default()
24176        },
24177        Some(tree_sitter_html::LANGUAGE.into()),
24178    ));
24179    language_registry.add(html_language);
24180    let mut fake_servers = language_registry.register_fake_lsp(
24181        "HTML",
24182        FakeLspAdapter {
24183            capabilities: lsp::ServerCapabilities {
24184                completion_provider: Some(lsp::CompletionOptions {
24185                    resolve_provider: Some(true),
24186                    ..Default::default()
24187                }),
24188                ..Default::default()
24189            },
24190            ..Default::default()
24191        },
24192    );
24193
24194    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24195    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24196
24197    let worktree_id = workspace
24198        .update(cx, |workspace, _window, cx| {
24199            workspace.project().update(cx, |project, cx| {
24200                project.worktrees(cx).next().unwrap().read(cx).id()
24201            })
24202        })
24203        .unwrap();
24204    project
24205        .update(cx, |project, cx| {
24206            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24207        })
24208        .await
24209        .unwrap();
24210    let editor = workspace
24211        .update(cx, |workspace, window, cx| {
24212            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24213        })
24214        .unwrap()
24215        .await
24216        .unwrap()
24217        .downcast::<Editor>()
24218        .unwrap();
24219
24220    let fake_server = fake_servers.next().await.unwrap();
24221    editor.update_in(cx, |editor, window, cx| {
24222        editor.set_text("<ad></ad>", window, cx);
24223        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24224            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24225        });
24226        set_linked_edit_ranges(
24227            (Point::new(0, 1), Point::new(0, 3)),
24228            (Point::new(0, 6), Point::new(0, 8)),
24229            editor,
24230            cx,
24231        );
24232    });
24233    let mut completion_handle =
24234        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24235            Ok(Some(lsp::CompletionResponse::Array(vec![
24236                lsp::CompletionItem {
24237                    label: "head".to_string(),
24238                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24239                        lsp::InsertReplaceEdit {
24240                            new_text: "head".to_string(),
24241                            insert: lsp::Range::new(
24242                                lsp::Position::new(0, 1),
24243                                lsp::Position::new(0, 3),
24244                            ),
24245                            replace: lsp::Range::new(
24246                                lsp::Position::new(0, 1),
24247                                lsp::Position::new(0, 3),
24248                            ),
24249                        },
24250                    )),
24251                    ..Default::default()
24252                },
24253            ])))
24254        });
24255    editor.update_in(cx, |editor, window, cx| {
24256        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24257    });
24258    cx.run_until_parked();
24259    completion_handle.next().await.unwrap();
24260    editor.update(cx, |editor, _| {
24261        assert!(
24262            editor.context_menu_visible(),
24263            "Completion menu should be visible"
24264        );
24265    });
24266    editor.update_in(cx, |editor, window, cx| {
24267        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24268    });
24269    cx.executor().run_until_parked();
24270    editor.update(cx, |editor, cx| {
24271        assert_eq!(editor.text(cx), "<head></head>");
24272    });
24273}
24274
24275#[gpui::test]
24276async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24277    init_test(cx, |_| {});
24278
24279    let mut cx = EditorTestContext::new(cx).await;
24280    let language = Arc::new(Language::new(
24281        LanguageConfig {
24282            name: "TSX".into(),
24283            matcher: LanguageMatcher {
24284                path_suffixes: vec!["tsx".to_string()],
24285                ..LanguageMatcher::default()
24286            },
24287            brackets: BracketPairConfig {
24288                pairs: vec![BracketPair {
24289                    start: "<".into(),
24290                    end: ">".into(),
24291                    close: true,
24292                    ..Default::default()
24293                }],
24294                ..Default::default()
24295            },
24296            linked_edit_characters: HashSet::from_iter(['.']),
24297            ..Default::default()
24298        },
24299        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24300    ));
24301    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24302
24303    // Test typing > does not extend linked pair
24304    cx.set_state("<divˇ<div></div>");
24305    cx.update_editor(|editor, _, cx| {
24306        set_linked_edit_ranges(
24307            (Point::new(0, 1), Point::new(0, 4)),
24308            (Point::new(0, 11), Point::new(0, 14)),
24309            editor,
24310            cx,
24311        );
24312    });
24313    cx.update_editor(|editor, window, cx| {
24314        editor.handle_input(">", window, cx);
24315    });
24316    cx.assert_editor_state("<div>ˇ<div></div>");
24317
24318    // Test typing . do extend linked pair
24319    cx.set_state("<Animatedˇ></Animated>");
24320    cx.update_editor(|editor, _, cx| {
24321        set_linked_edit_ranges(
24322            (Point::new(0, 1), Point::new(0, 9)),
24323            (Point::new(0, 12), Point::new(0, 20)),
24324            editor,
24325            cx,
24326        );
24327    });
24328    cx.update_editor(|editor, window, cx| {
24329        editor.handle_input(".", window, cx);
24330    });
24331    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24332    cx.update_editor(|editor, _, cx| {
24333        set_linked_edit_ranges(
24334            (Point::new(0, 1), Point::new(0, 10)),
24335            (Point::new(0, 13), Point::new(0, 21)),
24336            editor,
24337            cx,
24338        );
24339    });
24340    cx.update_editor(|editor, window, cx| {
24341        editor.handle_input("V", window, cx);
24342    });
24343    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24344}
24345
24346#[gpui::test]
24347async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24348    init_test(cx, |_| {});
24349
24350    let fs = FakeFs::new(cx.executor());
24351    fs.insert_tree(
24352        path!("/root"),
24353        json!({
24354            "a": {
24355                "main.rs": "fn main() {}",
24356            },
24357            "foo": {
24358                "bar": {
24359                    "external_file.rs": "pub mod external {}",
24360                }
24361            }
24362        }),
24363    )
24364    .await;
24365
24366    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24367    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24368    language_registry.add(rust_lang());
24369    let _fake_servers = language_registry.register_fake_lsp(
24370        "Rust",
24371        FakeLspAdapter {
24372            ..FakeLspAdapter::default()
24373        },
24374    );
24375    let (workspace, cx) =
24376        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24377    let worktree_id = workspace.update(cx, |workspace, cx| {
24378        workspace.project().update(cx, |project, cx| {
24379            project.worktrees(cx).next().unwrap().read(cx).id()
24380        })
24381    });
24382
24383    let assert_language_servers_count =
24384        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24385            project.update(cx, |project, cx| {
24386                let current = project
24387                    .lsp_store()
24388                    .read(cx)
24389                    .as_local()
24390                    .unwrap()
24391                    .language_servers
24392                    .len();
24393                assert_eq!(expected, current, "{context}");
24394            });
24395        };
24396
24397    assert_language_servers_count(
24398        0,
24399        "No servers should be running before any file is open",
24400        cx,
24401    );
24402    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24403    let main_editor = workspace
24404        .update_in(cx, |workspace, window, cx| {
24405            workspace.open_path(
24406                (worktree_id, rel_path("main.rs")),
24407                Some(pane.downgrade()),
24408                true,
24409                window,
24410                cx,
24411            )
24412        })
24413        .unwrap()
24414        .await
24415        .downcast::<Editor>()
24416        .unwrap();
24417    pane.update(cx, |pane, cx| {
24418        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24419        open_editor.update(cx, |editor, cx| {
24420            assert_eq!(
24421                editor.display_text(cx),
24422                "fn main() {}",
24423                "Original main.rs text on initial open",
24424            );
24425        });
24426        assert_eq!(open_editor, main_editor);
24427    });
24428    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24429
24430    let external_editor = workspace
24431        .update_in(cx, |workspace, window, cx| {
24432            workspace.open_abs_path(
24433                PathBuf::from("/root/foo/bar/external_file.rs"),
24434                OpenOptions::default(),
24435                window,
24436                cx,
24437            )
24438        })
24439        .await
24440        .expect("opening external file")
24441        .downcast::<Editor>()
24442        .expect("downcasted external file's open element to editor");
24443    pane.update(cx, |pane, cx| {
24444        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24445        open_editor.update(cx, |editor, cx| {
24446            assert_eq!(
24447                editor.display_text(cx),
24448                "pub mod external {}",
24449                "External file is open now",
24450            );
24451        });
24452        assert_eq!(open_editor, external_editor);
24453    });
24454    assert_language_servers_count(
24455        1,
24456        "Second, external, *.rs file should join the existing server",
24457        cx,
24458    );
24459
24460    pane.update_in(cx, |pane, window, cx| {
24461        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24462    })
24463    .await
24464    .unwrap();
24465    pane.update_in(cx, |pane, window, cx| {
24466        pane.navigate_backward(&Default::default(), window, cx);
24467    });
24468    cx.run_until_parked();
24469    pane.update(cx, |pane, cx| {
24470        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24471        open_editor.update(cx, |editor, cx| {
24472            assert_eq!(
24473                editor.display_text(cx),
24474                "pub mod external {}",
24475                "External file is open now",
24476            );
24477        });
24478    });
24479    assert_language_servers_count(
24480        1,
24481        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24482        cx,
24483    );
24484
24485    cx.update(|_, cx| {
24486        workspace::reload(cx);
24487    });
24488    assert_language_servers_count(
24489        1,
24490        "After reloading the worktree with local and external files opened, only one project should be started",
24491        cx,
24492    );
24493}
24494
24495#[gpui::test]
24496async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24497    init_test(cx, |_| {});
24498
24499    let mut cx = EditorTestContext::new(cx).await;
24500    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24501    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24502
24503    // test cursor move to start of each line on tab
24504    // for `if`, `elif`, `else`, `while`, `with` and `for`
24505    cx.set_state(indoc! {"
24506        def main():
24507        ˇ    for item in items:
24508        ˇ        while item.active:
24509        ˇ            if item.value > 10:
24510        ˇ                continue
24511        ˇ            elif item.value < 0:
24512        ˇ                break
24513        ˇ            else:
24514        ˇ                with item.context() as ctx:
24515        ˇ                    yield count
24516        ˇ        else:
24517        ˇ            log('while else')
24518        ˇ    else:
24519        ˇ        log('for else')
24520    "});
24521    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24522    cx.assert_editor_state(indoc! {"
24523        def main():
24524            ˇfor item in items:
24525                ˇwhile item.active:
24526                    ˇif item.value > 10:
24527                        ˇcontinue
24528                    ˇelif item.value < 0:
24529                        ˇbreak
24530                    ˇelse:
24531                        ˇwith item.context() as ctx:
24532                            ˇyield count
24533                ˇelse:
24534                    ˇlog('while else')
24535            ˇelse:
24536                ˇlog('for else')
24537    "});
24538    // test relative indent is preserved when tab
24539    // for `if`, `elif`, `else`, `while`, `with` and `for`
24540    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24541    cx.assert_editor_state(indoc! {"
24542        def main():
24543                ˇfor item in items:
24544                    ˇwhile item.active:
24545                        ˇif item.value > 10:
24546                            ˇcontinue
24547                        ˇelif item.value < 0:
24548                            ˇbreak
24549                        ˇelse:
24550                            ˇwith item.context() as ctx:
24551                                ˇyield count
24552                    ˇelse:
24553                        ˇlog('while else')
24554                ˇelse:
24555                    ˇlog('for else')
24556    "});
24557
24558    // test cursor move to start of each line on tab
24559    // for `try`, `except`, `else`, `finally`, `match` and `def`
24560    cx.set_state(indoc! {"
24561        def main():
24562        ˇ    try:
24563        ˇ        fetch()
24564        ˇ    except ValueError:
24565        ˇ        handle_error()
24566        ˇ    else:
24567        ˇ        match value:
24568        ˇ            case _:
24569        ˇ    finally:
24570        ˇ        def status():
24571        ˇ            return 0
24572    "});
24573    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24574    cx.assert_editor_state(indoc! {"
24575        def main():
24576            ˇtry:
24577                ˇfetch()
24578            ˇexcept ValueError:
24579                ˇhandle_error()
24580            ˇelse:
24581                ˇmatch value:
24582                    ˇcase _:
24583            ˇfinally:
24584                ˇdef status():
24585                    ˇreturn 0
24586    "});
24587    // test relative indent is preserved when tab
24588    // for `try`, `except`, `else`, `finally`, `match` and `def`
24589    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24590    cx.assert_editor_state(indoc! {"
24591        def main():
24592                ˇtry:
24593                    ˇfetch()
24594                ˇexcept ValueError:
24595                    ˇhandle_error()
24596                ˇelse:
24597                    ˇmatch value:
24598                        ˇcase _:
24599                ˇfinally:
24600                    ˇdef status():
24601                        ˇreturn 0
24602    "});
24603}
24604
24605#[gpui::test]
24606async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24607    init_test(cx, |_| {});
24608
24609    let mut cx = EditorTestContext::new(cx).await;
24610    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24611    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24612
24613    // test `else` auto outdents when typed inside `if` block
24614    cx.set_state(indoc! {"
24615        def main():
24616            if i == 2:
24617                return
24618                ˇ
24619    "});
24620    cx.update_editor(|editor, window, cx| {
24621        editor.handle_input("else:", window, cx);
24622    });
24623    cx.assert_editor_state(indoc! {"
24624        def main():
24625            if i == 2:
24626                return
24627            else:ˇ
24628    "});
24629
24630    // test `except` auto outdents when typed inside `try` block
24631    cx.set_state(indoc! {"
24632        def main():
24633            try:
24634                i = 2
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            except:ˇ
24645    "});
24646
24647    // test `else` auto outdents when typed inside `except` block
24648    cx.set_state(indoc! {"
24649        def main():
24650            try:
24651                i = 2
24652            except:
24653                j = 2
24654                ˇ
24655    "});
24656    cx.update_editor(|editor, window, cx| {
24657        editor.handle_input("else:", window, cx);
24658    });
24659    cx.assert_editor_state(indoc! {"
24660        def main():
24661            try:
24662                i = 2
24663            except:
24664                j = 2
24665            else:ˇ
24666    "});
24667
24668    // test `finally` auto outdents when typed inside `else` block
24669    cx.set_state(indoc! {"
24670        def main():
24671            try:
24672                i = 2
24673            except:
24674                j = 2
24675            else:
24676                k = 2
24677                ˇ
24678    "});
24679    cx.update_editor(|editor, window, cx| {
24680        editor.handle_input("finally:", window, cx);
24681    });
24682    cx.assert_editor_state(indoc! {"
24683        def main():
24684            try:
24685                i = 2
24686            except:
24687                j = 2
24688            else:
24689                k = 2
24690            finally:ˇ
24691    "});
24692
24693    // test `else` does not outdents when typed inside `except` block right after for block
24694    cx.set_state(indoc! {"
24695        def main():
24696            try:
24697                i = 2
24698            except:
24699                for i in range(n):
24700                    pass
24701                ˇ
24702    "});
24703    cx.update_editor(|editor, window, cx| {
24704        editor.handle_input("else:", window, cx);
24705    });
24706    cx.assert_editor_state(indoc! {"
24707        def main():
24708            try:
24709                i = 2
24710            except:
24711                for i in range(n):
24712                    pass
24713                else:ˇ
24714    "});
24715
24716    // test `finally` auto outdents when typed inside `else` block right after for block
24717    cx.set_state(indoc! {"
24718        def main():
24719            try:
24720                i = 2
24721            except:
24722                j = 2
24723            else:
24724                for i in range(n):
24725                    pass
24726                ˇ
24727    "});
24728    cx.update_editor(|editor, window, cx| {
24729        editor.handle_input("finally:", window, cx);
24730    });
24731    cx.assert_editor_state(indoc! {"
24732        def main():
24733            try:
24734                i = 2
24735            except:
24736                j = 2
24737            else:
24738                for i in range(n):
24739                    pass
24740            finally:ˇ
24741    "});
24742
24743    // test `except` outdents to inner "try" block
24744    cx.set_state(indoc! {"
24745        def main():
24746            try:
24747                i = 2
24748                if i == 2:
24749                    try:
24750                        i = 3
24751                        ˇ
24752    "});
24753    cx.update_editor(|editor, window, cx| {
24754        editor.handle_input("except:", window, cx);
24755    });
24756    cx.assert_editor_state(indoc! {"
24757        def main():
24758            try:
24759                i = 2
24760                if i == 2:
24761                    try:
24762                        i = 3
24763                    except:ˇ
24764    "});
24765
24766    // test `except` outdents to outer "try" block
24767    cx.set_state(indoc! {"
24768        def main():
24769            try:
24770                i = 2
24771                if i == 2:
24772                    try:
24773                        i = 3
24774                ˇ
24775    "});
24776    cx.update_editor(|editor, window, cx| {
24777        editor.handle_input("except:", window, cx);
24778    });
24779    cx.assert_editor_state(indoc! {"
24780        def main():
24781            try:
24782                i = 2
24783                if i == 2:
24784                    try:
24785                        i = 3
24786            except:ˇ
24787    "});
24788
24789    // test `else` stays at correct indent when typed after `for` block
24790    cx.set_state(indoc! {"
24791        def main():
24792            for i in range(10):
24793                if i == 3:
24794                    break
24795            ˇ
24796    "});
24797    cx.update_editor(|editor, window, cx| {
24798        editor.handle_input("else:", window, cx);
24799    });
24800    cx.assert_editor_state(indoc! {"
24801        def main():
24802            for i in range(10):
24803                if i == 3:
24804                    break
24805            else:ˇ
24806    "});
24807
24808    // test does not outdent on typing after line with square brackets
24809    cx.set_state(indoc! {"
24810        def f() -> list[str]:
24811            ˇ
24812    "});
24813    cx.update_editor(|editor, window, cx| {
24814        editor.handle_input("a", window, cx);
24815    });
24816    cx.assert_editor_state(indoc! {"
24817        def f() -> list[str]:
2481824819    "});
24820
24821    // test does not outdent on typing : after case keyword
24822    cx.set_state(indoc! {"
24823        match 1:
24824            caseˇ
24825    "});
24826    cx.update_editor(|editor, window, cx| {
24827        editor.handle_input(":", window, cx);
24828    });
24829    cx.assert_editor_state(indoc! {"
24830        match 1:
24831            case:ˇ
24832    "});
24833}
24834
24835#[gpui::test]
24836async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24837    init_test(cx, |_| {});
24838    update_test_language_settings(cx, |settings| {
24839        settings.defaults.extend_comment_on_newline = Some(false);
24840    });
24841    let mut cx = EditorTestContext::new(cx).await;
24842    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24843    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24844
24845    // test correct indent after newline on comment
24846    cx.set_state(indoc! {"
24847        # COMMENT:ˇ
24848    "});
24849    cx.update_editor(|editor, window, cx| {
24850        editor.newline(&Newline, window, cx);
24851    });
24852    cx.assert_editor_state(indoc! {"
24853        # COMMENT:
24854        ˇ
24855    "});
24856
24857    // test correct indent after newline in brackets
24858    cx.set_state(indoc! {"
24859        {ˇ}
24860    "});
24861    cx.update_editor(|editor, window, cx| {
24862        editor.newline(&Newline, window, cx);
24863    });
24864    cx.run_until_parked();
24865    cx.assert_editor_state(indoc! {"
24866        {
24867            ˇ
24868        }
24869    "});
24870
24871    cx.set_state(indoc! {"
24872        (ˇ)
24873    "});
24874    cx.update_editor(|editor, window, cx| {
24875        editor.newline(&Newline, window, cx);
24876    });
24877    cx.run_until_parked();
24878    cx.assert_editor_state(indoc! {"
24879        (
24880            ˇ
24881        )
24882    "});
24883
24884    // do not indent after empty lists or dictionaries
24885    cx.set_state(indoc! {"
24886        a = []ˇ
24887    "});
24888    cx.update_editor(|editor, window, cx| {
24889        editor.newline(&Newline, window, cx);
24890    });
24891    cx.run_until_parked();
24892    cx.assert_editor_state(indoc! {"
24893        a = []
24894        ˇ
24895    "});
24896}
24897
24898#[gpui::test]
24899async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24900    init_test(cx, |_| {});
24901
24902    let mut cx = EditorTestContext::new(cx).await;
24903    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24904    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24905
24906    // test cursor move to start of each line on tab
24907    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24908    cx.set_state(indoc! {"
24909        function main() {
24910        ˇ    for item in $items; do
24911        ˇ        while [ -n \"$item\" ]; do
24912        ˇ            if [ \"$value\" -gt 10 ]; then
24913        ˇ                continue
24914        ˇ            elif [ \"$value\" -lt 0 ]; then
24915        ˇ                break
24916        ˇ            else
24917        ˇ                echo \"$item\"
24918        ˇ            fi
24919        ˇ        done
24920        ˇ    done
24921        ˇ}
24922    "});
24923    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24924    cx.assert_editor_state(indoc! {"
24925        function main() {
24926            ˇfor item in $items; do
24927                ˇwhile [ -n \"$item\" ]; do
24928                    ˇif [ \"$value\" -gt 10 ]; then
24929                        ˇcontinue
24930                    ˇelif [ \"$value\" -lt 0 ]; then
24931                        ˇbreak
24932                    ˇelse
24933                        ˇecho \"$item\"
24934                    ˇfi
24935                ˇdone
24936            ˇdone
24937        ˇ}
24938    "});
24939    // test relative indent is preserved when tab
24940    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24941    cx.assert_editor_state(indoc! {"
24942        function main() {
24943                ˇfor item in $items; do
24944                    ˇwhile [ -n \"$item\" ]; do
24945                        ˇif [ \"$value\" -gt 10 ]; then
24946                            ˇcontinue
24947                        ˇelif [ \"$value\" -lt 0 ]; then
24948                            ˇbreak
24949                        ˇelse
24950                            ˇecho \"$item\"
24951                        ˇfi
24952                    ˇdone
24953                ˇdone
24954            ˇ}
24955    "});
24956
24957    // test cursor move to start of each line on tab
24958    // for `case` statement with patterns
24959    cx.set_state(indoc! {"
24960        function handle() {
24961        ˇ    case \"$1\" in
24962        ˇ        start)
24963        ˇ            echo \"a\"
24964        ˇ            ;;
24965        ˇ        stop)
24966        ˇ            echo \"b\"
24967        ˇ            ;;
24968        ˇ        *)
24969        ˇ            echo \"c\"
24970        ˇ            ;;
24971        ˇ    esac
24972        ˇ}
24973    "});
24974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24975    cx.assert_editor_state(indoc! {"
24976        function handle() {
24977            ˇcase \"$1\" in
24978                ˇstart)
24979                    ˇecho \"a\"
24980                    ˇ;;
24981                ˇstop)
24982                    ˇecho \"b\"
24983                    ˇ;;
24984                ˇ*)
24985                    ˇecho \"c\"
24986                    ˇ;;
24987            ˇesac
24988        ˇ}
24989    "});
24990}
24991
24992#[gpui::test]
24993async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24994    init_test(cx, |_| {});
24995
24996    let mut cx = EditorTestContext::new(cx).await;
24997    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24998    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24999
25000    // test indents on comment insert
25001    cx.set_state(indoc! {"
25002        function main() {
25003        ˇ    for item in $items; do
25004        ˇ        while [ -n \"$item\" ]; do
25005        ˇ            if [ \"$value\" -gt 10 ]; then
25006        ˇ                continue
25007        ˇ            elif [ \"$value\" -lt 0 ]; then
25008        ˇ                break
25009        ˇ            else
25010        ˇ                echo \"$item\"
25011        ˇ            fi
25012        ˇ        done
25013        ˇ    done
25014        ˇ}
25015    "});
25016    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25017    cx.assert_editor_state(indoc! {"
25018        function main() {
25019        #ˇ    for item in $items; do
25020        #ˇ        while [ -n \"$item\" ]; do
25021        #ˇ            if [ \"$value\" -gt 10 ]; then
25022        #ˇ                continue
25023        #ˇ            elif [ \"$value\" -lt 0 ]; then
25024        #ˇ                break
25025        #ˇ            else
25026        #ˇ                echo \"$item\"
25027        #ˇ            fi
25028        #ˇ        done
25029        #ˇ    done
25030        #ˇ}
25031    "});
25032}
25033
25034#[gpui::test]
25035async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25036    init_test(cx, |_| {});
25037
25038    let mut cx = EditorTestContext::new(cx).await;
25039    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25040    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25041
25042    // test `else` auto outdents when typed inside `if` block
25043    cx.set_state(indoc! {"
25044        if [ \"$1\" = \"test\" ]; then
25045            echo \"foo bar\"
25046            ˇ
25047    "});
25048    cx.update_editor(|editor, window, cx| {
25049        editor.handle_input("else", window, cx);
25050    });
25051    cx.assert_editor_state(indoc! {"
25052        if [ \"$1\" = \"test\" ]; then
25053            echo \"foo bar\"
25054        elseˇ
25055    "});
25056
25057    // test `elif` auto outdents when typed inside `if` block
25058    cx.set_state(indoc! {"
25059        if [ \"$1\" = \"test\" ]; then
25060            echo \"foo bar\"
25061            ˇ
25062    "});
25063    cx.update_editor(|editor, window, cx| {
25064        editor.handle_input("elif", window, cx);
25065    });
25066    cx.assert_editor_state(indoc! {"
25067        if [ \"$1\" = \"test\" ]; then
25068            echo \"foo bar\"
25069        elifˇ
25070    "});
25071
25072    // test `fi` auto outdents when typed inside `else` block
25073    cx.set_state(indoc! {"
25074        if [ \"$1\" = \"test\" ]; then
25075            echo \"foo bar\"
25076        else
25077            echo \"bar baz\"
25078            ˇ
25079    "});
25080    cx.update_editor(|editor, window, cx| {
25081        editor.handle_input("fi", window, cx);
25082    });
25083    cx.assert_editor_state(indoc! {"
25084        if [ \"$1\" = \"test\" ]; then
25085            echo \"foo bar\"
25086        else
25087            echo \"bar baz\"
25088        fiˇ
25089    "});
25090
25091    // test `done` auto outdents when typed inside `while` block
25092    cx.set_state(indoc! {"
25093        while read line; do
25094            echo \"$line\"
25095            ˇ
25096    "});
25097    cx.update_editor(|editor, window, cx| {
25098        editor.handle_input("done", window, cx);
25099    });
25100    cx.assert_editor_state(indoc! {"
25101        while read line; do
25102            echo \"$line\"
25103        doneˇ
25104    "});
25105
25106    // test `done` auto outdents when typed inside `for` block
25107    cx.set_state(indoc! {"
25108        for file in *.txt; do
25109            cat \"$file\"
25110            ˇ
25111    "});
25112    cx.update_editor(|editor, window, cx| {
25113        editor.handle_input("done", window, cx);
25114    });
25115    cx.assert_editor_state(indoc! {"
25116        for file in *.txt; do
25117            cat \"$file\"
25118        doneˇ
25119    "});
25120
25121    // test `esac` auto outdents when typed inside `case` block
25122    cx.set_state(indoc! {"
25123        case \"$1\" in
25124            start)
25125                echo \"foo bar\"
25126                ;;
25127            stop)
25128                echo \"bar baz\"
25129                ;;
25130            ˇ
25131    "});
25132    cx.update_editor(|editor, window, cx| {
25133        editor.handle_input("esac", window, cx);
25134    });
25135    cx.assert_editor_state(indoc! {"
25136        case \"$1\" in
25137            start)
25138                echo \"foo bar\"
25139                ;;
25140            stop)
25141                echo \"bar baz\"
25142                ;;
25143        esacˇ
25144    "});
25145
25146    // test `*)` auto outdents when typed inside `case` block
25147    cx.set_state(indoc! {"
25148        case \"$1\" in
25149            start)
25150                echo \"foo bar\"
25151                ;;
25152                ˇ
25153    "});
25154    cx.update_editor(|editor, window, cx| {
25155        editor.handle_input("*)", window, cx);
25156    });
25157    cx.assert_editor_state(indoc! {"
25158        case \"$1\" in
25159            start)
25160                echo \"foo bar\"
25161                ;;
25162            *)ˇ
25163    "});
25164
25165    // test `fi` outdents to correct level with nested if blocks
25166    cx.set_state(indoc! {"
25167        if [ \"$1\" = \"test\" ]; then
25168            echo \"outer if\"
25169            if [ \"$2\" = \"debug\" ]; then
25170                echo \"inner if\"
25171                ˇ
25172    "});
25173    cx.update_editor(|editor, window, cx| {
25174        editor.handle_input("fi", window, cx);
25175    });
25176    cx.assert_editor_state(indoc! {"
25177        if [ \"$1\" = \"test\" ]; then
25178            echo \"outer if\"
25179            if [ \"$2\" = \"debug\" ]; then
25180                echo \"inner if\"
25181            fiˇ
25182    "});
25183}
25184
25185#[gpui::test]
25186async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25187    init_test(cx, |_| {});
25188    update_test_language_settings(cx, |settings| {
25189        settings.defaults.extend_comment_on_newline = Some(false);
25190    });
25191    let mut cx = EditorTestContext::new(cx).await;
25192    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25193    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25194
25195    // test correct indent after newline on comment
25196    cx.set_state(indoc! {"
25197        # COMMENT:ˇ
25198    "});
25199    cx.update_editor(|editor, window, cx| {
25200        editor.newline(&Newline, window, cx);
25201    });
25202    cx.assert_editor_state(indoc! {"
25203        # COMMENT:
25204        ˇ
25205    "});
25206
25207    // test correct indent after newline after `then`
25208    cx.set_state(indoc! {"
25209
25210        if [ \"$1\" = \"test\" ]; thenˇ
25211    "});
25212    cx.update_editor(|editor, window, cx| {
25213        editor.newline(&Newline, window, cx);
25214    });
25215    cx.run_until_parked();
25216    cx.assert_editor_state(indoc! {"
25217
25218        if [ \"$1\" = \"test\" ]; then
25219            ˇ
25220    "});
25221
25222    // test correct indent after newline after `else`
25223    cx.set_state(indoc! {"
25224        if [ \"$1\" = \"test\" ]; then
25225        elseˇ
25226    "});
25227    cx.update_editor(|editor, window, cx| {
25228        editor.newline(&Newline, window, cx);
25229    });
25230    cx.run_until_parked();
25231    cx.assert_editor_state(indoc! {"
25232        if [ \"$1\" = \"test\" ]; then
25233        else
25234            ˇ
25235    "});
25236
25237    // test correct indent after newline after `elif`
25238    cx.set_state(indoc! {"
25239        if [ \"$1\" = \"test\" ]; then
25240        elifˇ
25241    "});
25242    cx.update_editor(|editor, window, cx| {
25243        editor.newline(&Newline, window, cx);
25244    });
25245    cx.run_until_parked();
25246    cx.assert_editor_state(indoc! {"
25247        if [ \"$1\" = \"test\" ]; then
25248        elif
25249            ˇ
25250    "});
25251
25252    // test correct indent after newline after `do`
25253    cx.set_state(indoc! {"
25254        for file in *.txt; doˇ
25255    "});
25256    cx.update_editor(|editor, window, cx| {
25257        editor.newline(&Newline, window, cx);
25258    });
25259    cx.run_until_parked();
25260    cx.assert_editor_state(indoc! {"
25261        for file in *.txt; do
25262            ˇ
25263    "});
25264
25265    // test correct indent after newline after case pattern
25266    cx.set_state(indoc! {"
25267        case \"$1\" in
25268            start)ˇ
25269    "});
25270    cx.update_editor(|editor, window, cx| {
25271        editor.newline(&Newline, window, cx);
25272    });
25273    cx.run_until_parked();
25274    cx.assert_editor_state(indoc! {"
25275        case \"$1\" in
25276            start)
25277                ˇ
25278    "});
25279
25280    // test correct indent after newline after case pattern
25281    cx.set_state(indoc! {"
25282        case \"$1\" in
25283            start)
25284                ;;
25285            *)ˇ
25286    "});
25287    cx.update_editor(|editor, window, cx| {
25288        editor.newline(&Newline, window, cx);
25289    });
25290    cx.run_until_parked();
25291    cx.assert_editor_state(indoc! {"
25292        case \"$1\" in
25293            start)
25294                ;;
25295            *)
25296                ˇ
25297    "});
25298
25299    // test correct indent after newline after function opening brace
25300    cx.set_state(indoc! {"
25301        function test() {ˇ}
25302    "});
25303    cx.update_editor(|editor, window, cx| {
25304        editor.newline(&Newline, window, cx);
25305    });
25306    cx.run_until_parked();
25307    cx.assert_editor_state(indoc! {"
25308        function test() {
25309            ˇ
25310        }
25311    "});
25312
25313    // test no extra indent after semicolon on same line
25314    cx.set_state(indoc! {"
25315        echo \"test\"25316    "});
25317    cx.update_editor(|editor, window, cx| {
25318        editor.newline(&Newline, window, cx);
25319    });
25320    cx.run_until_parked();
25321    cx.assert_editor_state(indoc! {"
25322        echo \"test\";
25323        ˇ
25324    "});
25325}
25326
25327fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25328    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25329    point..point
25330}
25331
25332#[track_caller]
25333fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25334    let (text, ranges) = marked_text_ranges(marked_text, true);
25335    assert_eq!(editor.text(cx), text);
25336    assert_eq!(
25337        editor.selections.ranges(&editor.display_snapshot(cx)),
25338        ranges,
25339        "Assert selections are {}",
25340        marked_text
25341    );
25342}
25343
25344pub fn handle_signature_help_request(
25345    cx: &mut EditorLspTestContext,
25346    mocked_response: lsp::SignatureHelp,
25347) -> impl Future<Output = ()> + use<> {
25348    let mut request =
25349        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25350            let mocked_response = mocked_response.clone();
25351            async move { Ok(Some(mocked_response)) }
25352        });
25353
25354    async move {
25355        request.next().await;
25356    }
25357}
25358
25359#[track_caller]
25360pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25361    cx.update_editor(|editor, _, _| {
25362        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25363            let entries = menu.entries.borrow();
25364            let entries = entries
25365                .iter()
25366                .map(|entry| entry.string.as_str())
25367                .collect::<Vec<_>>();
25368            assert_eq!(entries, expected);
25369        } else {
25370            panic!("Expected completions menu");
25371        }
25372    });
25373}
25374
25375/// Handle completion request passing a marked string specifying where the completion
25376/// should be triggered from using '|' character, what range should be replaced, and what completions
25377/// should be returned using '<' and '>' to delimit the range.
25378///
25379/// Also see `handle_completion_request_with_insert_and_replace`.
25380#[track_caller]
25381pub fn handle_completion_request(
25382    marked_string: &str,
25383    completions: Vec<&'static str>,
25384    is_incomplete: bool,
25385    counter: Arc<AtomicUsize>,
25386    cx: &mut EditorLspTestContext,
25387) -> impl Future<Output = ()> {
25388    let complete_from_marker: TextRangeMarker = '|'.into();
25389    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25390    let (_, mut marked_ranges) = marked_text_ranges_by(
25391        marked_string,
25392        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25393    );
25394
25395    let complete_from_position =
25396        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25397    let replace_range =
25398        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25399
25400    let mut request =
25401        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25402            let completions = completions.clone();
25403            counter.fetch_add(1, atomic::Ordering::Release);
25404            async move {
25405                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25406                assert_eq!(
25407                    params.text_document_position.position,
25408                    complete_from_position
25409                );
25410                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25411                    is_incomplete,
25412                    item_defaults: None,
25413                    items: completions
25414                        .iter()
25415                        .map(|completion_text| lsp::CompletionItem {
25416                            label: completion_text.to_string(),
25417                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25418                                range: replace_range,
25419                                new_text: completion_text.to_string(),
25420                            })),
25421                            ..Default::default()
25422                        })
25423                        .collect(),
25424                })))
25425            }
25426        });
25427
25428    async move {
25429        request.next().await;
25430    }
25431}
25432
25433/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25434/// given instead, which also contains an `insert` range.
25435///
25436/// This function uses markers to define ranges:
25437/// - `|` marks the cursor position
25438/// - `<>` marks the replace range
25439/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25440pub fn handle_completion_request_with_insert_and_replace(
25441    cx: &mut EditorLspTestContext,
25442    marked_string: &str,
25443    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25444    counter: Arc<AtomicUsize>,
25445) -> impl Future<Output = ()> {
25446    let complete_from_marker: TextRangeMarker = '|'.into();
25447    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25448    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25449
25450    let (_, mut marked_ranges) = marked_text_ranges_by(
25451        marked_string,
25452        vec![
25453            complete_from_marker.clone(),
25454            replace_range_marker.clone(),
25455            insert_range_marker.clone(),
25456        ],
25457    );
25458
25459    let complete_from_position =
25460        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25461    let replace_range =
25462        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25463
25464    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25465        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25466        _ => lsp::Range {
25467            start: replace_range.start,
25468            end: complete_from_position,
25469        },
25470    };
25471
25472    let mut request =
25473        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25474            let completions = completions.clone();
25475            counter.fetch_add(1, atomic::Ordering::Release);
25476            async move {
25477                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25478                assert_eq!(
25479                    params.text_document_position.position, complete_from_position,
25480                    "marker `|` position doesn't match",
25481                );
25482                Ok(Some(lsp::CompletionResponse::Array(
25483                    completions
25484                        .iter()
25485                        .map(|(label, new_text)| lsp::CompletionItem {
25486                            label: label.to_string(),
25487                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25488                                lsp::InsertReplaceEdit {
25489                                    insert: insert_range,
25490                                    replace: replace_range,
25491                                    new_text: new_text.to_string(),
25492                                },
25493                            )),
25494                            ..Default::default()
25495                        })
25496                        .collect(),
25497                )))
25498            }
25499        });
25500
25501    async move {
25502        request.next().await;
25503    }
25504}
25505
25506fn handle_resolve_completion_request(
25507    cx: &mut EditorLspTestContext,
25508    edits: Option<Vec<(&'static str, &'static str)>>,
25509) -> impl Future<Output = ()> {
25510    let edits = edits.map(|edits| {
25511        edits
25512            .iter()
25513            .map(|(marked_string, new_text)| {
25514                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25515                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25516                lsp::TextEdit::new(replace_range, new_text.to_string())
25517            })
25518            .collect::<Vec<_>>()
25519    });
25520
25521    let mut request =
25522        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25523            let edits = edits.clone();
25524            async move {
25525                Ok(lsp::CompletionItem {
25526                    additional_text_edits: edits,
25527                    ..Default::default()
25528                })
25529            }
25530        });
25531
25532    async move {
25533        request.next().await;
25534    }
25535}
25536
25537pub(crate) fn update_test_language_settings(
25538    cx: &mut TestAppContext,
25539    f: impl Fn(&mut AllLanguageSettingsContent),
25540) {
25541    cx.update(|cx| {
25542        SettingsStore::update_global(cx, |store, cx| {
25543            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25544        });
25545    });
25546}
25547
25548pub(crate) fn update_test_project_settings(
25549    cx: &mut TestAppContext,
25550    f: impl Fn(&mut ProjectSettingsContent),
25551) {
25552    cx.update(|cx| {
25553        SettingsStore::update_global(cx, |store, cx| {
25554            store.update_user_settings(cx, |settings| f(&mut settings.project));
25555        });
25556    });
25557}
25558
25559pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25560    cx.update(|cx| {
25561        assets::Assets.load_test_fonts(cx);
25562        let store = SettingsStore::test(cx);
25563        cx.set_global(store);
25564        theme::init(theme::LoadThemes::JustBase, cx);
25565        release_channel::init(SemanticVersion::default(), cx);
25566        client::init_settings(cx);
25567        language::init(cx);
25568        Project::init_settings(cx);
25569        workspace::init_settings(cx);
25570        crate::init(cx);
25571    });
25572    zlog::init_test();
25573    update_test_language_settings(cx, f);
25574}
25575
25576#[track_caller]
25577fn assert_hunk_revert(
25578    not_reverted_text_with_selections: &str,
25579    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25580    expected_reverted_text_with_selections: &str,
25581    base_text: &str,
25582    cx: &mut EditorLspTestContext,
25583) {
25584    cx.set_state(not_reverted_text_with_selections);
25585    cx.set_head_text(base_text);
25586    cx.executor().run_until_parked();
25587
25588    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25589        let snapshot = editor.snapshot(window, cx);
25590        let reverted_hunk_statuses = snapshot
25591            .buffer_snapshot()
25592            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25593            .map(|hunk| hunk.status().kind)
25594            .collect::<Vec<_>>();
25595
25596        editor.git_restore(&Default::default(), window, cx);
25597        reverted_hunk_statuses
25598    });
25599    cx.executor().run_until_parked();
25600    cx.assert_editor_state(expected_reverted_text_with_selections);
25601    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25602}
25603
25604#[gpui::test(iterations = 10)]
25605async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25606    init_test(cx, |_| {});
25607
25608    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25609    let counter = diagnostic_requests.clone();
25610
25611    let fs = FakeFs::new(cx.executor());
25612    fs.insert_tree(
25613        path!("/a"),
25614        json!({
25615            "first.rs": "fn main() { let a = 5; }",
25616            "second.rs": "// Test file",
25617        }),
25618    )
25619    .await;
25620
25621    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25622    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25623    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25624
25625    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25626    language_registry.add(rust_lang());
25627    let mut fake_servers = language_registry.register_fake_lsp(
25628        "Rust",
25629        FakeLspAdapter {
25630            capabilities: lsp::ServerCapabilities {
25631                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25632                    lsp::DiagnosticOptions {
25633                        identifier: None,
25634                        inter_file_dependencies: true,
25635                        workspace_diagnostics: true,
25636                        work_done_progress_options: Default::default(),
25637                    },
25638                )),
25639                ..Default::default()
25640            },
25641            ..Default::default()
25642        },
25643    );
25644
25645    let editor = workspace
25646        .update(cx, |workspace, window, cx| {
25647            workspace.open_abs_path(
25648                PathBuf::from(path!("/a/first.rs")),
25649                OpenOptions::default(),
25650                window,
25651                cx,
25652            )
25653        })
25654        .unwrap()
25655        .await
25656        .unwrap()
25657        .downcast::<Editor>()
25658        .unwrap();
25659    let fake_server = fake_servers.next().await.unwrap();
25660    let server_id = fake_server.server.server_id();
25661    let mut first_request = fake_server
25662        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25663            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25664            let result_id = Some(new_result_id.to_string());
25665            assert_eq!(
25666                params.text_document.uri,
25667                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25668            );
25669            async move {
25670                Ok(lsp::DocumentDiagnosticReportResult::Report(
25671                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25672                        related_documents: None,
25673                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25674                            items: Vec::new(),
25675                            result_id,
25676                        },
25677                    }),
25678                ))
25679            }
25680        });
25681
25682    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25683        project.update(cx, |project, cx| {
25684            let buffer_id = editor
25685                .read(cx)
25686                .buffer()
25687                .read(cx)
25688                .as_singleton()
25689                .expect("created a singleton buffer")
25690                .read(cx)
25691                .remote_id();
25692            let buffer_result_id = project
25693                .lsp_store()
25694                .read(cx)
25695                .result_id(server_id, buffer_id, cx);
25696            assert_eq!(expected, buffer_result_id);
25697        });
25698    };
25699
25700    ensure_result_id(None, cx);
25701    cx.executor().advance_clock(Duration::from_millis(60));
25702    cx.executor().run_until_parked();
25703    assert_eq!(
25704        diagnostic_requests.load(atomic::Ordering::Acquire),
25705        1,
25706        "Opening file should trigger diagnostic request"
25707    );
25708    first_request
25709        .next()
25710        .await
25711        .expect("should have sent the first diagnostics pull request");
25712    ensure_result_id(Some("1".to_string()), cx);
25713
25714    // Editing should trigger diagnostics
25715    editor.update_in(cx, |editor, window, cx| {
25716        editor.handle_input("2", window, cx)
25717    });
25718    cx.executor().advance_clock(Duration::from_millis(60));
25719    cx.executor().run_until_parked();
25720    assert_eq!(
25721        diagnostic_requests.load(atomic::Ordering::Acquire),
25722        2,
25723        "Editing should trigger diagnostic request"
25724    );
25725    ensure_result_id(Some("2".to_string()), cx);
25726
25727    // Moving cursor should not trigger diagnostic request
25728    editor.update_in(cx, |editor, window, cx| {
25729        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25730            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25731        });
25732    });
25733    cx.executor().advance_clock(Duration::from_millis(60));
25734    cx.executor().run_until_parked();
25735    assert_eq!(
25736        diagnostic_requests.load(atomic::Ordering::Acquire),
25737        2,
25738        "Cursor movement should not trigger diagnostic request"
25739    );
25740    ensure_result_id(Some("2".to_string()), cx);
25741    // Multiple rapid edits should be debounced
25742    for _ in 0..5 {
25743        editor.update_in(cx, |editor, window, cx| {
25744            editor.handle_input("x", window, cx)
25745        });
25746    }
25747    cx.executor().advance_clock(Duration::from_millis(60));
25748    cx.executor().run_until_parked();
25749
25750    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25751    assert!(
25752        final_requests <= 4,
25753        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25754    );
25755    ensure_result_id(Some(final_requests.to_string()), cx);
25756}
25757
25758#[gpui::test]
25759async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25760    // Regression test for issue #11671
25761    // Previously, adding a cursor after moving multiple cursors would reset
25762    // the cursor count instead of adding to the existing cursors.
25763    init_test(cx, |_| {});
25764    let mut cx = EditorTestContext::new(cx).await;
25765
25766    // Create a simple buffer with cursor at start
25767    cx.set_state(indoc! {"
25768        ˇaaaa
25769        bbbb
25770        cccc
25771        dddd
25772        eeee
25773        ffff
25774        gggg
25775        hhhh"});
25776
25777    // Add 2 cursors below (so we have 3 total)
25778    cx.update_editor(|editor, window, cx| {
25779        editor.add_selection_below(&Default::default(), window, cx);
25780        editor.add_selection_below(&Default::default(), window, cx);
25781    });
25782
25783    // Verify we have 3 cursors
25784    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25785    assert_eq!(
25786        initial_count, 3,
25787        "Should have 3 cursors after adding 2 below"
25788    );
25789
25790    // Move down one line
25791    cx.update_editor(|editor, window, cx| {
25792        editor.move_down(&MoveDown, window, cx);
25793    });
25794
25795    // Add another cursor below
25796    cx.update_editor(|editor, window, cx| {
25797        editor.add_selection_below(&Default::default(), window, cx);
25798    });
25799
25800    // Should now have 4 cursors (3 original + 1 new)
25801    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25802    assert_eq!(
25803        final_count, 4,
25804        "Should have 4 cursors after moving and adding another"
25805    );
25806}
25807
25808#[gpui::test]
25809async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25810    init_test(cx, |_| {});
25811
25812    let mut cx = EditorTestContext::new(cx).await;
25813
25814    cx.set_state(indoc!(
25815        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25816           Second line here"#
25817    ));
25818
25819    cx.update_editor(|editor, window, cx| {
25820        // Enable soft wrapping with a narrow width to force soft wrapping and
25821        // confirm that more than 2 rows are being displayed.
25822        editor.set_wrap_width(Some(100.0.into()), cx);
25823        assert!(editor.display_text(cx).lines().count() > 2);
25824
25825        editor.add_selection_below(
25826            &AddSelectionBelow {
25827                skip_soft_wrap: true,
25828            },
25829            window,
25830            cx,
25831        );
25832
25833        assert_eq!(
25834            editor.selections.display_ranges(cx),
25835            &[
25836                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25837                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25838            ]
25839        );
25840
25841        editor.add_selection_above(
25842            &AddSelectionAbove {
25843                skip_soft_wrap: true,
25844            },
25845            window,
25846            cx,
25847        );
25848
25849        assert_eq!(
25850            editor.selections.display_ranges(cx),
25851            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25852        );
25853
25854        editor.add_selection_below(
25855            &AddSelectionBelow {
25856                skip_soft_wrap: false,
25857            },
25858            window,
25859            cx,
25860        );
25861
25862        assert_eq!(
25863            editor.selections.display_ranges(cx),
25864            &[
25865                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25866                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25867            ]
25868        );
25869
25870        editor.add_selection_above(
25871            &AddSelectionAbove {
25872                skip_soft_wrap: false,
25873            },
25874            window,
25875            cx,
25876        );
25877
25878        assert_eq!(
25879            editor.selections.display_ranges(cx),
25880            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25881        );
25882    });
25883}
25884
25885#[gpui::test(iterations = 10)]
25886async fn test_document_colors(cx: &mut TestAppContext) {
25887    let expected_color = Rgba {
25888        r: 0.33,
25889        g: 0.33,
25890        b: 0.33,
25891        a: 0.33,
25892    };
25893
25894    init_test(cx, |_| {});
25895
25896    let fs = FakeFs::new(cx.executor());
25897    fs.insert_tree(
25898        path!("/a"),
25899        json!({
25900            "first.rs": "fn main() { let a = 5; }",
25901        }),
25902    )
25903    .await;
25904
25905    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25906    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25907    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25908
25909    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25910    language_registry.add(rust_lang());
25911    let mut fake_servers = language_registry.register_fake_lsp(
25912        "Rust",
25913        FakeLspAdapter {
25914            capabilities: lsp::ServerCapabilities {
25915                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25916                ..lsp::ServerCapabilities::default()
25917            },
25918            name: "rust-analyzer",
25919            ..FakeLspAdapter::default()
25920        },
25921    );
25922    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25923        "Rust",
25924        FakeLspAdapter {
25925            capabilities: lsp::ServerCapabilities {
25926                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25927                ..lsp::ServerCapabilities::default()
25928            },
25929            name: "not-rust-analyzer",
25930            ..FakeLspAdapter::default()
25931        },
25932    );
25933
25934    let editor = workspace
25935        .update(cx, |workspace, window, cx| {
25936            workspace.open_abs_path(
25937                PathBuf::from(path!("/a/first.rs")),
25938                OpenOptions::default(),
25939                window,
25940                cx,
25941            )
25942        })
25943        .unwrap()
25944        .await
25945        .unwrap()
25946        .downcast::<Editor>()
25947        .unwrap();
25948    let fake_language_server = fake_servers.next().await.unwrap();
25949    let fake_language_server_without_capabilities =
25950        fake_servers_without_capabilities.next().await.unwrap();
25951    let requests_made = Arc::new(AtomicUsize::new(0));
25952    let closure_requests_made = Arc::clone(&requests_made);
25953    let mut color_request_handle = fake_language_server
25954        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25955            let requests_made = Arc::clone(&closure_requests_made);
25956            async move {
25957                assert_eq!(
25958                    params.text_document.uri,
25959                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25960                );
25961                requests_made.fetch_add(1, atomic::Ordering::Release);
25962                Ok(vec![
25963                    lsp::ColorInformation {
25964                        range: lsp::Range {
25965                            start: lsp::Position {
25966                                line: 0,
25967                                character: 0,
25968                            },
25969                            end: lsp::Position {
25970                                line: 0,
25971                                character: 1,
25972                            },
25973                        },
25974                        color: lsp::Color {
25975                            red: 0.33,
25976                            green: 0.33,
25977                            blue: 0.33,
25978                            alpha: 0.33,
25979                        },
25980                    },
25981                    lsp::ColorInformation {
25982                        range: lsp::Range {
25983                            start: lsp::Position {
25984                                line: 0,
25985                                character: 0,
25986                            },
25987                            end: lsp::Position {
25988                                line: 0,
25989                                character: 1,
25990                            },
25991                        },
25992                        color: lsp::Color {
25993                            red: 0.33,
25994                            green: 0.33,
25995                            blue: 0.33,
25996                            alpha: 0.33,
25997                        },
25998                    },
25999                ])
26000            }
26001        });
26002
26003    let _handle = fake_language_server_without_capabilities
26004        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26005            panic!("Should not be called");
26006        });
26007    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26008    color_request_handle.next().await.unwrap();
26009    cx.run_until_parked();
26010    assert_eq!(
26011        1,
26012        requests_made.load(atomic::Ordering::Acquire),
26013        "Should query for colors once per editor open"
26014    );
26015    editor.update_in(cx, |editor, _, cx| {
26016        assert_eq!(
26017            vec![expected_color],
26018            extract_color_inlays(editor, cx),
26019            "Should have an initial inlay"
26020        );
26021    });
26022
26023    // opening another file in a split should not influence the LSP query counter
26024    workspace
26025        .update(cx, |workspace, window, cx| {
26026            assert_eq!(
26027                workspace.panes().len(),
26028                1,
26029                "Should have one pane with one editor"
26030            );
26031            workspace.move_item_to_pane_in_direction(
26032                &MoveItemToPaneInDirection {
26033                    direction: SplitDirection::Right,
26034                    focus: false,
26035                    clone: true,
26036                },
26037                window,
26038                cx,
26039            );
26040        })
26041        .unwrap();
26042    cx.run_until_parked();
26043    workspace
26044        .update(cx, |workspace, _, cx| {
26045            let panes = workspace.panes();
26046            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26047            for pane in panes {
26048                let editor = pane
26049                    .read(cx)
26050                    .active_item()
26051                    .and_then(|item| item.downcast::<Editor>())
26052                    .expect("Should have opened an editor in each split");
26053                let editor_file = editor
26054                    .read(cx)
26055                    .buffer()
26056                    .read(cx)
26057                    .as_singleton()
26058                    .expect("test deals with singleton buffers")
26059                    .read(cx)
26060                    .file()
26061                    .expect("test buffese should have a file")
26062                    .path();
26063                assert_eq!(
26064                    editor_file.as_ref(),
26065                    rel_path("first.rs"),
26066                    "Both editors should be opened for the same file"
26067                )
26068            }
26069        })
26070        .unwrap();
26071
26072    cx.executor().advance_clock(Duration::from_millis(500));
26073    let save = editor.update_in(cx, |editor, window, cx| {
26074        editor.move_to_end(&MoveToEnd, window, cx);
26075        editor.handle_input("dirty", window, cx);
26076        editor.save(
26077            SaveOptions {
26078                format: true,
26079                autosave: true,
26080            },
26081            project.clone(),
26082            window,
26083            cx,
26084        )
26085    });
26086    save.await.unwrap();
26087
26088    color_request_handle.next().await.unwrap();
26089    cx.run_until_parked();
26090    assert_eq!(
26091        2,
26092        requests_made.load(atomic::Ordering::Acquire),
26093        "Should query for colors once per save (deduplicated) and once per formatting after save"
26094    );
26095
26096    drop(editor);
26097    let close = workspace
26098        .update(cx, |workspace, window, cx| {
26099            workspace.active_pane().update(cx, |pane, cx| {
26100                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26101            })
26102        })
26103        .unwrap();
26104    close.await.unwrap();
26105    let close = workspace
26106        .update(cx, |workspace, window, cx| {
26107            workspace.active_pane().update(cx, |pane, cx| {
26108                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26109            })
26110        })
26111        .unwrap();
26112    close.await.unwrap();
26113    assert_eq!(
26114        2,
26115        requests_made.load(atomic::Ordering::Acquire),
26116        "After saving and closing all editors, no extra requests should be made"
26117    );
26118    workspace
26119        .update(cx, |workspace, _, cx| {
26120            assert!(
26121                workspace.active_item(cx).is_none(),
26122                "Should close all editors"
26123            )
26124        })
26125        .unwrap();
26126
26127    workspace
26128        .update(cx, |workspace, window, cx| {
26129            workspace.active_pane().update(cx, |pane, cx| {
26130                pane.navigate_backward(&workspace::GoBack, window, cx);
26131            })
26132        })
26133        .unwrap();
26134    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26135    cx.run_until_parked();
26136    let editor = workspace
26137        .update(cx, |workspace, _, cx| {
26138            workspace
26139                .active_item(cx)
26140                .expect("Should have reopened the editor again after navigating back")
26141                .downcast::<Editor>()
26142                .expect("Should be an editor")
26143        })
26144        .unwrap();
26145
26146    assert_eq!(
26147        2,
26148        requests_made.load(atomic::Ordering::Acquire),
26149        "Cache should be reused on buffer close and reopen"
26150    );
26151    editor.update(cx, |editor, cx| {
26152        assert_eq!(
26153            vec![expected_color],
26154            extract_color_inlays(editor, cx),
26155            "Should have an initial inlay"
26156        );
26157    });
26158
26159    drop(color_request_handle);
26160    let closure_requests_made = Arc::clone(&requests_made);
26161    let mut empty_color_request_handle = fake_language_server
26162        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26163            let requests_made = Arc::clone(&closure_requests_made);
26164            async move {
26165                assert_eq!(
26166                    params.text_document.uri,
26167                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26168                );
26169                requests_made.fetch_add(1, atomic::Ordering::Release);
26170                Ok(Vec::new())
26171            }
26172        });
26173    let save = editor.update_in(cx, |editor, window, cx| {
26174        editor.move_to_end(&MoveToEnd, window, cx);
26175        editor.handle_input("dirty_again", window, cx);
26176        editor.save(
26177            SaveOptions {
26178                format: false,
26179                autosave: true,
26180            },
26181            project.clone(),
26182            window,
26183            cx,
26184        )
26185    });
26186    save.await.unwrap();
26187
26188    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26189    empty_color_request_handle.next().await.unwrap();
26190    cx.run_until_parked();
26191    assert_eq!(
26192        3,
26193        requests_made.load(atomic::Ordering::Acquire),
26194        "Should query for colors once per save only, as formatting was not requested"
26195    );
26196    editor.update(cx, |editor, cx| {
26197        assert_eq!(
26198            Vec::<Rgba>::new(),
26199            extract_color_inlays(editor, cx),
26200            "Should clear all colors when the server returns an empty response"
26201        );
26202    });
26203}
26204
26205#[gpui::test]
26206async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26207    init_test(cx, |_| {});
26208    let (editor, cx) = cx.add_window_view(Editor::single_line);
26209    editor.update_in(cx, |editor, window, cx| {
26210        editor.set_text("oops\n\nwow\n", window, cx)
26211    });
26212    cx.run_until_parked();
26213    editor.update(cx, |editor, cx| {
26214        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26215    });
26216    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26217    cx.run_until_parked();
26218    editor.update(cx, |editor, cx| {
26219        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26220    });
26221}
26222
26223#[gpui::test]
26224async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26225    init_test(cx, |_| {});
26226
26227    cx.update(|cx| {
26228        register_project_item::<Editor>(cx);
26229    });
26230
26231    let fs = FakeFs::new(cx.executor());
26232    fs.insert_tree("/root1", json!({})).await;
26233    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26234        .await;
26235
26236    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26237    let (workspace, cx) =
26238        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26239
26240    let worktree_id = project.update(cx, |project, cx| {
26241        project.worktrees(cx).next().unwrap().read(cx).id()
26242    });
26243
26244    let handle = workspace
26245        .update_in(cx, |workspace, window, cx| {
26246            let project_path = (worktree_id, rel_path("one.pdf"));
26247            workspace.open_path(project_path, None, true, window, cx)
26248        })
26249        .await
26250        .unwrap();
26251
26252    assert_eq!(
26253        handle.to_any().entity_type(),
26254        TypeId::of::<InvalidItemView>()
26255    );
26256}
26257
26258#[gpui::test]
26259async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26260    init_test(cx, |_| {});
26261
26262    let language = Arc::new(Language::new(
26263        LanguageConfig::default(),
26264        Some(tree_sitter_rust::LANGUAGE.into()),
26265    ));
26266
26267    // Test hierarchical sibling navigation
26268    let text = r#"
26269        fn outer() {
26270            if condition {
26271                let a = 1;
26272            }
26273            let b = 2;
26274        }
26275
26276        fn another() {
26277            let c = 3;
26278        }
26279    "#;
26280
26281    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26282    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26283    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26284
26285    // Wait for parsing to complete
26286    editor
26287        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26288        .await;
26289
26290    editor.update_in(cx, |editor, window, cx| {
26291        // Start by selecting "let a = 1;" inside the if block
26292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26293            s.select_display_ranges([
26294                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26295            ]);
26296        });
26297
26298        let initial_selection = editor.selections.display_ranges(cx);
26299        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26300
26301        // Test select next sibling - should move up levels to find the next sibling
26302        // Since "let a = 1;" has no siblings in the if block, it should move up
26303        // to find "let b = 2;" which is a sibling of the if block
26304        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26305        let next_selection = editor.selections.display_ranges(cx);
26306
26307        // Should have a selection and it should be different from the initial
26308        assert_eq!(
26309            next_selection.len(),
26310            1,
26311            "Should have one selection after next"
26312        );
26313        assert_ne!(
26314            next_selection[0], initial_selection[0],
26315            "Next sibling selection should be different"
26316        );
26317
26318        // Test hierarchical navigation by going to the end of the current function
26319        // and trying to navigate to the next function
26320        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26321            s.select_display_ranges([
26322                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26323            ]);
26324        });
26325
26326        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26327        let function_next_selection = editor.selections.display_ranges(cx);
26328
26329        // Should move to the next function
26330        assert_eq!(
26331            function_next_selection.len(),
26332            1,
26333            "Should have one selection after function next"
26334        );
26335
26336        // Test select previous sibling navigation
26337        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26338        let prev_selection = editor.selections.display_ranges(cx);
26339
26340        // Should have a selection and it should be different
26341        assert_eq!(
26342            prev_selection.len(),
26343            1,
26344            "Should have one selection after prev"
26345        );
26346        assert_ne!(
26347            prev_selection[0], function_next_selection[0],
26348            "Previous sibling selection should be different from next"
26349        );
26350    });
26351}
26352
26353#[gpui::test]
26354async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26355    init_test(cx, |_| {});
26356
26357    let mut cx = EditorTestContext::new(cx).await;
26358    cx.set_state(
26359        "let ˇvariable = 42;
26360let another = variable + 1;
26361let result = variable * 2;",
26362    );
26363
26364    // Set up document highlights manually (simulating LSP response)
26365    cx.update_editor(|editor, _window, cx| {
26366        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26367
26368        // Create highlights for "variable" occurrences
26369        let highlight_ranges = [
26370            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26371            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26372            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26373        ];
26374
26375        let anchor_ranges: Vec<_> = highlight_ranges
26376            .iter()
26377            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26378            .collect();
26379
26380        editor.highlight_background::<DocumentHighlightRead>(
26381            &anchor_ranges,
26382            |theme| theme.colors().editor_document_highlight_read_background,
26383            cx,
26384        );
26385    });
26386
26387    // Go to next highlight - should move to second "variable"
26388    cx.update_editor(|editor, window, cx| {
26389        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26390    });
26391    cx.assert_editor_state(
26392        "let variable = 42;
26393let another = ˇvariable + 1;
26394let result = variable * 2;",
26395    );
26396
26397    // Go to next highlight - should move to third "variable"
26398    cx.update_editor(|editor, window, cx| {
26399        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26400    });
26401    cx.assert_editor_state(
26402        "let variable = 42;
26403let another = variable + 1;
26404let result = ˇvariable * 2;",
26405    );
26406
26407    // Go to next highlight - should stay at third "variable" (no wrap-around)
26408    cx.update_editor(|editor, window, cx| {
26409        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26410    });
26411    cx.assert_editor_state(
26412        "let variable = 42;
26413let another = variable + 1;
26414let result = ˇvariable * 2;",
26415    );
26416
26417    // Now test going backwards from third position
26418    cx.update_editor(|editor, window, cx| {
26419        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26420    });
26421    cx.assert_editor_state(
26422        "let variable = 42;
26423let another = ˇvariable + 1;
26424let result = variable * 2;",
26425    );
26426
26427    // Go to previous highlight - should move to first "variable"
26428    cx.update_editor(|editor, window, cx| {
26429        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26430    });
26431    cx.assert_editor_state(
26432        "let ˇvariable = 42;
26433let another = variable + 1;
26434let result = variable * 2;",
26435    );
26436
26437    // Go to previous highlight - should stay on first "variable"
26438    cx.update_editor(|editor, window, cx| {
26439        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26440    });
26441    cx.assert_editor_state(
26442        "let ˇvariable = 42;
26443let another = variable + 1;
26444let result = variable * 2;",
26445    );
26446}
26447
26448#[gpui::test]
26449async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26450    cx: &mut gpui::TestAppContext,
26451) {
26452    init_test(cx, |_| {});
26453
26454    let url = "https://zed.dev";
26455
26456    let markdown_language = Arc::new(Language::new(
26457        LanguageConfig {
26458            name: "Markdown".into(),
26459            ..LanguageConfig::default()
26460        },
26461        None,
26462    ));
26463
26464    let mut cx = EditorTestContext::new(cx).await;
26465    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26466    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26467
26468    cx.update_editor(|editor, window, cx| {
26469        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26470        editor.paste(&Paste, window, cx);
26471    });
26472
26473    cx.assert_editor_state(&format!(
26474        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26475    ));
26476}
26477
26478#[gpui::test]
26479async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26480    cx: &mut gpui::TestAppContext,
26481) {
26482    init_test(cx, |_| {});
26483
26484    let url = "https://zed.dev";
26485
26486    let markdown_language = Arc::new(Language::new(
26487        LanguageConfig {
26488            name: "Markdown".into(),
26489            ..LanguageConfig::default()
26490        },
26491        None,
26492    ));
26493
26494    let mut cx = EditorTestContext::new(cx).await;
26495    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26496    cx.set_state(&format!(
26497        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26498    ));
26499
26500    cx.update_editor(|editor, window, cx| {
26501        editor.copy(&Copy, window, cx);
26502    });
26503
26504    cx.set_state(&format!(
26505        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26506    ));
26507
26508    cx.update_editor(|editor, window, cx| {
26509        editor.paste(&Paste, window, cx);
26510    });
26511
26512    cx.assert_editor_state(&format!(
26513        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26514    ));
26515}
26516
26517#[gpui::test]
26518async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26519    cx: &mut gpui::TestAppContext,
26520) {
26521    init_test(cx, |_| {});
26522
26523    let url = "https://zed.dev";
26524
26525    let markdown_language = Arc::new(Language::new(
26526        LanguageConfig {
26527            name: "Markdown".into(),
26528            ..LanguageConfig::default()
26529        },
26530        None,
26531    ));
26532
26533    let mut cx = EditorTestContext::new(cx).await;
26534    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26535    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26536
26537    cx.update_editor(|editor, window, cx| {
26538        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26539        editor.paste(&Paste, window, cx);
26540    });
26541
26542    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26543}
26544
26545#[gpui::test]
26546async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26547    cx: &mut gpui::TestAppContext,
26548) {
26549    init_test(cx, |_| {});
26550
26551    let text = "Awesome";
26552
26553    let markdown_language = Arc::new(Language::new(
26554        LanguageConfig {
26555            name: "Markdown".into(),
26556            ..LanguageConfig::default()
26557        },
26558        None,
26559    ));
26560
26561    let mut cx = EditorTestContext::new(cx).await;
26562    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26563    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26564
26565    cx.update_editor(|editor, window, cx| {
26566        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26567        editor.paste(&Paste, window, cx);
26568    });
26569
26570    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26571}
26572
26573#[gpui::test]
26574async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26575    cx: &mut gpui::TestAppContext,
26576) {
26577    init_test(cx, |_| {});
26578
26579    let url = "https://zed.dev";
26580
26581    let markdown_language = Arc::new(Language::new(
26582        LanguageConfig {
26583            name: "Rust".into(),
26584            ..LanguageConfig::default()
26585        },
26586        None,
26587    ));
26588
26589    let mut cx = EditorTestContext::new(cx).await;
26590    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26591    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26592
26593    cx.update_editor(|editor, window, cx| {
26594        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26595        editor.paste(&Paste, window, cx);
26596    });
26597
26598    cx.assert_editor_state(&format!(
26599        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26600    ));
26601}
26602
26603#[gpui::test]
26604async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26605    cx: &mut TestAppContext,
26606) {
26607    init_test(cx, |_| {});
26608
26609    let url = "https://zed.dev";
26610
26611    let markdown_language = Arc::new(Language::new(
26612        LanguageConfig {
26613            name: "Markdown".into(),
26614            ..LanguageConfig::default()
26615        },
26616        None,
26617    ));
26618
26619    let (editor, cx) = cx.add_window_view(|window, cx| {
26620        let multi_buffer = MultiBuffer::build_multi(
26621            [
26622                ("this will embed -> link", vec![Point::row_range(0..1)]),
26623                ("this will replace -> link", vec![Point::row_range(0..1)]),
26624            ],
26625            cx,
26626        );
26627        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26628        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26629            s.select_ranges(vec![
26630                Point::new(0, 19)..Point::new(0, 23),
26631                Point::new(1, 21)..Point::new(1, 25),
26632            ])
26633        });
26634        let first_buffer_id = multi_buffer
26635            .read(cx)
26636            .excerpt_buffer_ids()
26637            .into_iter()
26638            .next()
26639            .unwrap();
26640        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26641        first_buffer.update(cx, |buffer, cx| {
26642            buffer.set_language(Some(markdown_language.clone()), cx);
26643        });
26644
26645        editor
26646    });
26647    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26648
26649    cx.update_editor(|editor, window, cx| {
26650        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26651        editor.paste(&Paste, window, cx);
26652    });
26653
26654    cx.assert_editor_state(&format!(
26655        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26656    ));
26657}
26658
26659#[gpui::test]
26660async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26661    init_test(cx, |_| {});
26662
26663    let fs = FakeFs::new(cx.executor());
26664    fs.insert_tree(
26665        path!("/project"),
26666        json!({
26667            "first.rs": "# First Document\nSome content here.",
26668            "second.rs": "Plain text content for second file.",
26669        }),
26670    )
26671    .await;
26672
26673    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26674    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26675    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26676
26677    let language = rust_lang();
26678    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26679    language_registry.add(language.clone());
26680    let mut fake_servers = language_registry.register_fake_lsp(
26681        "Rust",
26682        FakeLspAdapter {
26683            ..FakeLspAdapter::default()
26684        },
26685    );
26686
26687    let buffer1 = project
26688        .update(cx, |project, cx| {
26689            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26690        })
26691        .await
26692        .unwrap();
26693    let buffer2 = project
26694        .update(cx, |project, cx| {
26695            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26696        })
26697        .await
26698        .unwrap();
26699
26700    let multi_buffer = cx.new(|cx| {
26701        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26702        multi_buffer.set_excerpts_for_path(
26703            PathKey::for_buffer(&buffer1, cx),
26704            buffer1.clone(),
26705            [Point::zero()..buffer1.read(cx).max_point()],
26706            3,
26707            cx,
26708        );
26709        multi_buffer.set_excerpts_for_path(
26710            PathKey::for_buffer(&buffer2, cx),
26711            buffer2.clone(),
26712            [Point::zero()..buffer1.read(cx).max_point()],
26713            3,
26714            cx,
26715        );
26716        multi_buffer
26717    });
26718
26719    let (editor, cx) = cx.add_window_view(|window, cx| {
26720        Editor::new(
26721            EditorMode::full(),
26722            multi_buffer,
26723            Some(project.clone()),
26724            window,
26725            cx,
26726        )
26727    });
26728
26729    let fake_language_server = fake_servers.next().await.unwrap();
26730
26731    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26732
26733    let save = editor.update_in(cx, |editor, window, cx| {
26734        assert!(editor.is_dirty(cx));
26735
26736        editor.save(
26737            SaveOptions {
26738                format: true,
26739                autosave: true,
26740            },
26741            project,
26742            window,
26743            cx,
26744        )
26745    });
26746    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26747    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26748    let mut done_edit_rx = Some(done_edit_rx);
26749    let mut start_edit_tx = Some(start_edit_tx);
26750
26751    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26752        start_edit_tx.take().unwrap().send(()).unwrap();
26753        let done_edit_rx = done_edit_rx.take().unwrap();
26754        async move {
26755            done_edit_rx.await.unwrap();
26756            Ok(None)
26757        }
26758    });
26759
26760    start_edit_rx.await.unwrap();
26761    buffer2
26762        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26763        .unwrap();
26764
26765    done_edit_tx.send(()).unwrap();
26766
26767    save.await.unwrap();
26768    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26769}
26770
26771#[track_caller]
26772fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26773    editor
26774        .all_inlays(cx)
26775        .into_iter()
26776        .filter_map(|inlay| inlay.get_color())
26777        .map(Rgba::from)
26778        .collect()
26779}
26780
26781#[gpui::test]
26782fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26783    init_test(cx, |_| {});
26784
26785    let editor = cx.add_window(|window, cx| {
26786        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26787        build_editor(buffer, window, cx)
26788    });
26789
26790    editor
26791        .update(cx, |editor, window, cx| {
26792            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26793                s.select_display_ranges([
26794                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26795                ])
26796            });
26797
26798            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26799
26800            assert_eq!(
26801                editor.display_text(cx),
26802                "line1\nline2\nline2",
26803                "Duplicating last line upward should create duplicate above, not on same line"
26804            );
26805
26806            assert_eq!(
26807                editor.selections.display_ranges(cx),
26808                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26809                "Selection should move to the duplicated line"
26810            );
26811        })
26812        .unwrap();
26813}
26814
26815#[gpui::test]
26816async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26817    init_test(cx, |_| {});
26818
26819    let mut cx = EditorTestContext::new(cx).await;
26820
26821    cx.set_state("line1\nline2ˇ");
26822
26823    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26824
26825    let clipboard_text = cx
26826        .read_from_clipboard()
26827        .and_then(|item| item.text().as_deref().map(str::to_string));
26828
26829    assert_eq!(
26830        clipboard_text,
26831        Some("line2\n".to_string()),
26832        "Copying a line without trailing newline should include a newline"
26833    );
26834
26835    cx.set_state("line1\nˇ");
26836
26837    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26838
26839    cx.assert_editor_state("line1\nline2\nˇ");
26840}
26841
26842#[gpui::test]
26843async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26844    init_test(cx, |_| {});
26845
26846    let mut cx = EditorTestContext::new(cx).await;
26847
26848    cx.set_state("line1\nline2ˇ");
26849    cx.update_editor(|e, window, cx| {
26850        e.set_mode(EditorMode::SingleLine);
26851        assert!(e.key_context(window, cx).contains("end_of_input"));
26852    });
26853    cx.set_state("ˇline1\nline2");
26854    cx.update_editor(|e, window, cx| {
26855        assert!(!e.key_context(window, cx).contains("end_of_input"));
26856    });
26857    cx.set_state("line1ˇ\nline2");
26858    cx.update_editor(|e, window, cx| {
26859        assert!(!e.key_context(window, cx).contains("end_of_input"));
26860    });
26861}
26862
26863#[gpui::test]
26864async fn test_next_prev_reference(cx: &mut TestAppContext) {
26865    const CYCLE_POSITIONS: &[&'static str] = &[
26866        indoc! {"
26867            fn foo() {
26868                let ˇabc = 123;
26869                let x = abc + 1;
26870                let y = abc + 2;
26871                let z = abc + 2;
26872            }
26873        "},
26874        indoc! {"
26875            fn foo() {
26876                let abc = 123;
26877                let x = ˇabc + 1;
26878                let y = abc + 2;
26879                let z = abc + 2;
26880            }
26881        "},
26882        indoc! {"
26883            fn foo() {
26884                let abc = 123;
26885                let x = abc + 1;
26886                let y = ˇabc + 2;
26887                let z = abc + 2;
26888            }
26889        "},
26890        indoc! {"
26891            fn foo() {
26892                let abc = 123;
26893                let x = abc + 1;
26894                let y = abc + 2;
26895                let z = ˇabc + 2;
26896            }
26897        "},
26898    ];
26899
26900    init_test(cx, |_| {});
26901
26902    let mut cx = EditorLspTestContext::new_rust(
26903        lsp::ServerCapabilities {
26904            references_provider: Some(lsp::OneOf::Left(true)),
26905            ..Default::default()
26906        },
26907        cx,
26908    )
26909    .await;
26910
26911    // importantly, the cursor is in the middle
26912    cx.set_state(indoc! {"
26913        fn foo() {
26914            let aˇbc = 123;
26915            let x = abc + 1;
26916            let y = abc + 2;
26917            let z = abc + 2;
26918        }
26919    "});
26920
26921    let reference_ranges = [
26922        lsp::Position::new(1, 8),
26923        lsp::Position::new(2, 12),
26924        lsp::Position::new(3, 12),
26925        lsp::Position::new(4, 12),
26926    ]
26927    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
26928
26929    cx.lsp
26930        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
26931            Ok(Some(
26932                reference_ranges
26933                    .map(|range| lsp::Location {
26934                        uri: params.text_document_position.text_document.uri.clone(),
26935                        range,
26936                    })
26937                    .to_vec(),
26938            ))
26939        });
26940
26941    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
26942        cx.update_editor(|editor, window, cx| {
26943            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
26944        })
26945        .unwrap()
26946        .await
26947        .unwrap()
26948    };
26949
26950    _move(Direction::Next, 1, &mut cx).await;
26951    cx.assert_editor_state(CYCLE_POSITIONS[1]);
26952
26953    _move(Direction::Next, 1, &mut cx).await;
26954    cx.assert_editor_state(CYCLE_POSITIONS[2]);
26955
26956    _move(Direction::Next, 1, &mut cx).await;
26957    cx.assert_editor_state(CYCLE_POSITIONS[3]);
26958
26959    // loops back to the start
26960    _move(Direction::Next, 1, &mut cx).await;
26961    cx.assert_editor_state(CYCLE_POSITIONS[0]);
26962
26963    // loops back to the end
26964    _move(Direction::Prev, 1, &mut cx).await;
26965    cx.assert_editor_state(CYCLE_POSITIONS[3]);
26966
26967    _move(Direction::Prev, 1, &mut cx).await;
26968    cx.assert_editor_state(CYCLE_POSITIONS[2]);
26969
26970    _move(Direction::Prev, 1, &mut cx).await;
26971    cx.assert_editor_state(CYCLE_POSITIONS[1]);
26972
26973    _move(Direction::Prev, 1, &mut cx).await;
26974    cx.assert_editor_state(CYCLE_POSITIONS[0]);
26975
26976    _move(Direction::Next, 3, &mut cx).await;
26977    cx.assert_editor_state(CYCLE_POSITIONS[3]);
26978
26979    _move(Direction::Prev, 2, &mut cx).await;
26980    cx.assert_editor_state(CYCLE_POSITIONS[1]);
26981}