editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    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 futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  184
  185        editor.backspace(&Backspace, window, cx);
  186    });
  187    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  188}
  189
  190#[gpui::test]
  191fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  192    init_test(cx, |_| {});
  193
  194    let mut now = Instant::now();
  195    let group_interval = Duration::from_millis(1);
  196    let buffer = cx.new(|cx| {
  197        let mut buf = language::Buffer::local("123456", cx);
  198        buf.set_group_interval(group_interval);
  199        buf
  200    });
  201    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  202    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  203
  204    _ = editor.update(cx, |editor, window, cx| {
  205        editor.start_transaction_at(now, window, cx);
  206        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  207
  208        editor.insert("cd", window, cx);
  209        editor.end_transaction_at(now, cx);
  210        assert_eq!(editor.text(cx), "12cd56");
  211        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  212
  213        editor.start_transaction_at(now, window, cx);
  214        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  215        editor.insert("e", window, cx);
  216        editor.end_transaction_at(now, cx);
  217        assert_eq!(editor.text(cx), "12cde6");
  218        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  219
  220        now += group_interval + Duration::from_millis(1);
  221        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  222
  223        // Simulate an edit in another editor
  224        buffer.update(cx, |buffer, cx| {
  225            buffer.start_transaction_at(now, cx);
  226            buffer.edit([(0..1, "a")], None, cx);
  227            buffer.edit([(1..1, "b")], None, cx);
  228            buffer.end_transaction_at(now, cx);
  229        });
  230
  231        assert_eq!(editor.text(cx), "ab2cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  233
  234        // Last transaction happened past the group interval in a different editor.
  235        // Undo it individually and don't restore selections.
  236        editor.undo(&Undo, window, cx);
  237        assert_eq!(editor.text(cx), "12cde6");
  238        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  239
  240        // First two transactions happened within the group interval in this editor.
  241        // Undo them together and restore selections.
  242        editor.undo(&Undo, window, cx);
  243        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  244        assert_eq!(editor.text(cx), "123456");
  245        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  246
  247        // Redo the first two transactions together.
  248        editor.redo(&Redo, window, cx);
  249        assert_eq!(editor.text(cx), "12cde6");
  250        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  251
  252        // Redo the last transaction on its own.
  253        editor.redo(&Redo, window, cx);
  254        assert_eq!(editor.text(cx), "ab2cde6");
  255        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  256
  257        // Test empty transactions.
  258        editor.start_transaction_at(now, window, cx);
  259        editor.end_transaction_at(now, cx);
  260        editor.undo(&Undo, window, cx);
  261        assert_eq!(editor.text(cx), "12cde6");
  262    });
  263}
  264
  265#[gpui::test]
  266fn test_ime_composition(cx: &mut TestAppContext) {
  267    init_test(cx, |_| {});
  268
  269    let buffer = cx.new(|cx| {
  270        let mut buffer = language::Buffer::local("abcde", cx);
  271        // Ensure automatic grouping doesn't occur.
  272        buffer.set_group_interval(Duration::ZERO);
  273        buffer
  274    });
  275
  276    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  277    cx.add_window(|window, cx| {
  278        let mut editor = build_editor(buffer.clone(), window, cx);
  279
  280        // Start a new IME composition.
  281        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  282        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  283        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  284        assert_eq!(editor.text(cx), "äbcde");
  285        assert_eq!(
  286            editor.marked_text_ranges(cx),
  287            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  288        );
  289
  290        // Finalize IME composition.
  291        editor.replace_text_in_range(None, "ā", window, cx);
  292        assert_eq!(editor.text(cx), "ābcde");
  293        assert_eq!(editor.marked_text_ranges(cx), None);
  294
  295        // IME composition edits are grouped and are undone/redone at once.
  296        editor.undo(&Default::default(), window, cx);
  297        assert_eq!(editor.text(cx), "abcde");
  298        assert_eq!(editor.marked_text_ranges(cx), None);
  299        editor.redo(&Default::default(), window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // Start a new IME composition.
  304        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  305        assert_eq!(
  306            editor.marked_text_ranges(cx),
  307            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  308        );
  309
  310        // Undoing during an IME composition cancels it.
  311        editor.undo(&Default::default(), window, cx);
  312        assert_eq!(editor.text(cx), "ābcde");
  313        assert_eq!(editor.marked_text_ranges(cx), None);
  314
  315        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  316        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  317        assert_eq!(editor.text(cx), "ābcdè");
  318        assert_eq!(
  319            editor.marked_text_ranges(cx),
  320            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  321        );
  322
  323        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  324        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  325        assert_eq!(editor.text(cx), "ābcdę");
  326        assert_eq!(editor.marked_text_ranges(cx), None);
  327
  328        // Start a new IME composition with multiple cursors.
  329        editor.change_selections(None, window, cx, |s| {
  330            s.select_ranges([
  331                OffsetUtf16(1)..OffsetUtf16(1),
  332                OffsetUtf16(3)..OffsetUtf16(3),
  333                OffsetUtf16(5)..OffsetUtf16(5),
  334            ])
  335        });
  336        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  337        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  338        assert_eq!(
  339            editor.marked_text_ranges(cx),
  340            Some(vec![
  341                OffsetUtf16(0)..OffsetUtf16(3),
  342                OffsetUtf16(4)..OffsetUtf16(7),
  343                OffsetUtf16(8)..OffsetUtf16(11)
  344            ])
  345        );
  346
  347        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  348        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  349        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  350        assert_eq!(
  351            editor.marked_text_ranges(cx),
  352            Some(vec![
  353                OffsetUtf16(1)..OffsetUtf16(2),
  354                OffsetUtf16(5)..OffsetUtf16(6),
  355                OffsetUtf16(9)..OffsetUtf16(10)
  356            ])
  357        );
  358
  359        // Finalize IME composition with multiple cursors.
  360        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  361        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  362        assert_eq!(editor.marked_text_ranges(cx), None);
  363
  364        editor
  365    });
  366}
  367
  368#[gpui::test]
  369fn test_selection_with_mouse(cx: &mut TestAppContext) {
  370    init_test(cx, |_| {});
  371
  372    let editor = cx.add_window(|window, cx| {
  373        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  374        build_editor(buffer, window, cx)
  375    });
  376
  377    _ = editor.update(cx, |editor, window, cx| {
  378        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  379    });
  380    assert_eq!(
  381        editor
  382            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  383            .unwrap(),
  384        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  385    );
  386
  387    _ = editor.update(cx, |editor, window, cx| {
  388        editor.update_selection(
  389            DisplayPoint::new(DisplayRow(3), 3),
  390            0,
  391            gpui::Point::<f32>::default(),
  392            window,
  393            cx,
  394        );
  395    });
  396
  397    assert_eq!(
  398        editor
  399            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  400            .unwrap(),
  401        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  402    );
  403
  404    _ = editor.update(cx, |editor, window, cx| {
  405        editor.update_selection(
  406            DisplayPoint::new(DisplayRow(1), 1),
  407            0,
  408            gpui::Point::<f32>::default(),
  409            window,
  410            cx,
  411        );
  412    });
  413
  414    assert_eq!(
  415        editor
  416            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  417            .unwrap(),
  418        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  419    );
  420
  421    _ = editor.update(cx, |editor, window, cx| {
  422        editor.end_selection(window, cx);
  423        editor.update_selection(
  424            DisplayPoint::new(DisplayRow(3), 3),
  425            0,
  426            gpui::Point::<f32>::default(),
  427            window,
  428            cx,
  429        );
  430    });
  431
  432    assert_eq!(
  433        editor
  434            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  435            .unwrap(),
  436        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  437    );
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  441        editor.update_selection(
  442            DisplayPoint::new(DisplayRow(0), 0),
  443            0,
  444            gpui::Point::<f32>::default(),
  445            window,
  446            cx,
  447        );
  448    });
  449
  450    assert_eq!(
  451        editor
  452            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  453            .unwrap(),
  454        [
  455            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  456            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  457        ]
  458    );
  459
  460    _ = editor.update(cx, |editor, window, cx| {
  461        editor.end_selection(window, cx);
  462    });
  463
  464    assert_eq!(
  465        editor
  466            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  467            .unwrap(),
  468        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  469    );
  470}
  471
  472#[gpui::test]
  473fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  474    init_test(cx, |_| {});
  475
  476    let editor = cx.add_window(|window, cx| {
  477        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  478        build_editor(buffer, window, cx)
  479    });
  480
  481    _ = editor.update(cx, |editor, window, cx| {
  482        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  483    });
  484
  485    _ = editor.update(cx, |editor, window, cx| {
  486        editor.end_selection(window, cx);
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    assert_eq!(
  498        editor
  499            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  500            .unwrap(),
  501        [
  502            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  503            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  504        ]
  505    );
  506
  507    _ = editor.update(cx, |editor, window, cx| {
  508        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  509    });
  510
  511    _ = editor.update(cx, |editor, window, cx| {
  512        editor.end_selection(window, cx);
  513    });
  514
  515    assert_eq!(
  516        editor
  517            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  518            .unwrap(),
  519        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  520    );
  521}
  522
  523#[gpui::test]
  524fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  525    init_test(cx, |_| {});
  526
  527    let editor = cx.add_window(|window, cx| {
  528        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  529        build_editor(buffer, window, cx)
  530    });
  531
  532    _ = editor.update(cx, |editor, window, cx| {
  533        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  534        assert_eq!(
  535            editor.selections.display_ranges(cx),
  536            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  537        );
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.update_selection(
  542            DisplayPoint::new(DisplayRow(3), 3),
  543            0,
  544            gpui::Point::<f32>::default(),
  545            window,
  546            cx,
  547        );
  548        assert_eq!(
  549            editor.selections.display_ranges(cx),
  550            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  551        );
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.cancel(&Cancel, window, cx);
  556        editor.update_selection(
  557            DisplayPoint::new(DisplayRow(1), 1),
  558            0,
  559            gpui::Point::<f32>::default(),
  560            window,
  561            cx,
  562        );
  563        assert_eq!(
  564            editor.selections.display_ranges(cx),
  565            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  566        );
  567    });
  568}
  569
  570#[gpui::test]
  571fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  572    init_test(cx, |_| {});
  573
  574    let editor = cx.add_window(|window, cx| {
  575        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  576        build_editor(buffer, window, cx)
  577    });
  578
  579    _ = editor.update(cx, |editor, window, cx| {
  580        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  581        assert_eq!(
  582            editor.selections.display_ranges(cx),
  583            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  584        );
  585
  586        editor.move_down(&Default::default(), window, cx);
  587        assert_eq!(
  588            editor.selections.display_ranges(cx),
  589            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  590        );
  591
  592        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  593        assert_eq!(
  594            editor.selections.display_ranges(cx),
  595            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  596        );
  597
  598        editor.move_up(&Default::default(), window, cx);
  599        assert_eq!(
  600            editor.selections.display_ranges(cx),
  601            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  602        );
  603    });
  604}
  605
  606#[gpui::test]
  607fn test_clone(cx: &mut TestAppContext) {
  608    init_test(cx, |_| {});
  609
  610    let (text, selection_ranges) = marked_text_ranges(
  611        indoc! {"
  612            one
  613            two
  614            threeˇ
  615            four
  616            fiveˇ
  617        "},
  618        true,
  619    );
  620
  621    let editor = cx.add_window(|window, cx| {
  622        let buffer = MultiBuffer::build_simple(&text, cx);
  623        build_editor(buffer, window, cx)
  624    });
  625
  626    _ = editor.update(cx, |editor, window, cx| {
  627        editor.change_selections(None, window, cx, |s| {
  628            s.select_ranges(selection_ranges.clone())
  629        });
  630        editor.fold_creases(
  631            vec![
  632                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  633                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  634            ],
  635            true,
  636            window,
  637            cx,
  638        );
  639    });
  640
  641    let cloned_editor = editor
  642        .update(cx, |editor, _, cx| {
  643            cx.open_window(Default::default(), |window, cx| {
  644                cx.new(|cx| editor.clone(window, cx))
  645            })
  646        })
  647        .unwrap()
  648        .unwrap();
  649
  650    let snapshot = editor
  651        .update(cx, |e, window, cx| e.snapshot(window, cx))
  652        .unwrap();
  653    let cloned_snapshot = cloned_editor
  654        .update(cx, |e, window, cx| e.snapshot(window, cx))
  655        .unwrap();
  656
  657    assert_eq!(
  658        cloned_editor
  659            .update(cx, |e, _, cx| e.display_text(cx))
  660            .unwrap(),
  661        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  662    );
  663    assert_eq!(
  664        cloned_snapshot
  665            .folds_in_range(0..text.len())
  666            .collect::<Vec<_>>(),
  667        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  668    );
  669    assert_set_eq!(
  670        cloned_editor
  671            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  672            .unwrap(),
  673        editor
  674            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  675            .unwrap()
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  683            .unwrap()
  684    );
  685}
  686
  687#[gpui::test]
  688async fn test_navigation_history(cx: &mut TestAppContext) {
  689    init_test(cx, |_| {});
  690
  691    use workspace::item::Item;
  692
  693    let fs = FakeFs::new(cx.executor());
  694    let project = Project::test(fs, [], cx).await;
  695    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  696    let pane = workspace
  697        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  698        .unwrap();
  699
  700    _ = workspace.update(cx, |_v, window, cx| {
  701        cx.new(|cx| {
  702            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  703            let mut editor = build_editor(buffer.clone(), window, cx);
  704            let handle = cx.entity();
  705            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  706
  707            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  708                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  709            }
  710
  711            // Move the cursor a small distance.
  712            // Nothing is added to the navigation history.
  713            editor.change_selections(None, window, cx, |s| {
  714                s.select_display_ranges([
  715                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  716                ])
  717            });
  718            editor.change_selections(None, window, cx, |s| {
  719                s.select_display_ranges([
  720                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  721                ])
  722            });
  723            assert!(pop_history(&mut editor, cx).is_none());
  724
  725            // Move the cursor a large distance.
  726            // The history can jump back to the previous position.
  727            editor.change_selections(None, window, cx, |s| {
  728                s.select_display_ranges([
  729                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  730                ])
  731            });
  732            let nav_entry = pop_history(&mut editor, cx).unwrap();
  733            editor.navigate(nav_entry.data.unwrap(), window, cx);
  734            assert_eq!(nav_entry.item.id(), cx.entity_id());
  735            assert_eq!(
  736                editor.selections.display_ranges(cx),
  737                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  738            );
  739            assert!(pop_history(&mut editor, cx).is_none());
  740
  741            // Move the cursor a small distance via the mouse.
  742            // Nothing is added to the navigation history.
  743            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  744            editor.end_selection(window, cx);
  745            assert_eq!(
  746                editor.selections.display_ranges(cx),
  747                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  748            );
  749            assert!(pop_history(&mut editor, cx).is_none());
  750
  751            // Move the cursor a large distance via the mouse.
  752            // The history can jump back to the previous position.
  753            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  754            editor.end_selection(window, cx);
  755            assert_eq!(
  756                editor.selections.display_ranges(cx),
  757                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  758            );
  759            let nav_entry = pop_history(&mut editor, cx).unwrap();
  760            editor.navigate(nav_entry.data.unwrap(), window, cx);
  761            assert_eq!(nav_entry.item.id(), cx.entity_id());
  762            assert_eq!(
  763                editor.selections.display_ranges(cx),
  764                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  765            );
  766            assert!(pop_history(&mut editor, cx).is_none());
  767
  768            // Set scroll position to check later
  769            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  770            let original_scroll_position = editor.scroll_manager.anchor();
  771
  772            // Jump to the end of the document and adjust scroll
  773            editor.move_to_end(&MoveToEnd, window, cx);
  774            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  775            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  776
  777            let nav_entry = pop_history(&mut editor, cx).unwrap();
  778            editor.navigate(nav_entry.data.unwrap(), window, cx);
  779            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  780
  781            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  782            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  783            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  784            let invalid_point = Point::new(9999, 0);
  785            editor.navigate(
  786                Box::new(NavigationData {
  787                    cursor_anchor: invalid_anchor,
  788                    cursor_position: invalid_point,
  789                    scroll_anchor: ScrollAnchor {
  790                        anchor: invalid_anchor,
  791                        offset: Default::default(),
  792                    },
  793                    scroll_top_row: invalid_point.row,
  794                }),
  795                window,
  796                cx,
  797            );
  798            assert_eq!(
  799                editor.selections.display_ranges(cx),
  800                &[editor.max_point(cx)..editor.max_point(cx)]
  801            );
  802            assert_eq!(
  803                editor.scroll_position(cx),
  804                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  805            );
  806
  807            editor
  808        })
  809    });
  810}
  811
  812#[gpui::test]
  813fn test_cancel(cx: &mut TestAppContext) {
  814    init_test(cx, |_| {});
  815
  816    let editor = cx.add_window(|window, cx| {
  817        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  818        build_editor(buffer, window, cx)
  819    });
  820
  821    _ = editor.update(cx, |editor, window, cx| {
  822        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  823        editor.update_selection(
  824            DisplayPoint::new(DisplayRow(1), 1),
  825            0,
  826            gpui::Point::<f32>::default(),
  827            window,
  828            cx,
  829        );
  830        editor.end_selection(window, cx);
  831
  832        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  833        editor.update_selection(
  834            DisplayPoint::new(DisplayRow(0), 3),
  835            0,
  836            gpui::Point::<f32>::default(),
  837            window,
  838            cx,
  839        );
  840        editor.end_selection(window, cx);
  841        assert_eq!(
  842            editor.selections.display_ranges(cx),
  843            [
  844                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  845                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  846            ]
  847        );
  848    });
  849
  850    _ = editor.update(cx, |editor, window, cx| {
  851        editor.cancel(&Cancel, window, cx);
  852        assert_eq!(
  853            editor.selections.display_ranges(cx),
  854            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865}
  866
  867#[gpui::test]
  868fn test_fold_action(cx: &mut TestAppContext) {
  869    init_test(cx, |_| {});
  870
  871    let editor = cx.add_window(|window, cx| {
  872        let buffer = MultiBuffer::build_simple(
  873            &"
  874                impl Foo {
  875                    // Hello!
  876
  877                    fn a() {
  878                        1
  879                    }
  880
  881                    fn b() {
  882                        2
  883                    }
  884
  885                    fn c() {
  886                        3
  887                    }
  888                }
  889            "
  890            .unindent(),
  891            cx,
  892        );
  893        build_editor(buffer.clone(), window, cx)
  894    });
  895
  896    _ = editor.update(cx, |editor, window, cx| {
  897        editor.change_selections(None, window, cx, |s| {
  898            s.select_display_ranges([
  899                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  900            ]);
  901        });
  902        editor.fold(&Fold, window, cx);
  903        assert_eq!(
  904            editor.display_text(cx),
  905            "
  906                impl Foo {
  907                    // Hello!
  908
  909                    fn a() {
  910                        1
  911                    }
  912
  913                    fn b() {⋯
  914                    }
  915
  916                    fn c() {⋯
  917                    }
  918                }
  919            "
  920            .unindent(),
  921        );
  922
  923        editor.fold(&Fold, window, cx);
  924        assert_eq!(
  925            editor.display_text(cx),
  926            "
  927                impl Foo {⋯
  928                }
  929            "
  930            .unindent(),
  931        );
  932
  933        editor.unfold_lines(&UnfoldLines, window, cx);
  934        assert_eq!(
  935            editor.display_text(cx),
  936            "
  937                impl Foo {
  938                    // Hello!
  939
  940                    fn a() {
  941                        1
  942                    }
  943
  944                    fn b() {⋯
  945                    }
  946
  947                    fn c() {⋯
  948                    }
  949                }
  950            "
  951            .unindent(),
  952        );
  953
  954        editor.unfold_lines(&UnfoldLines, window, cx);
  955        assert_eq!(
  956            editor.display_text(cx),
  957            editor.buffer.read(cx).read(cx).text()
  958        );
  959    });
  960}
  961
  962#[gpui::test]
  963fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  964    init_test(cx, |_| {});
  965
  966    let editor = cx.add_window(|window, cx| {
  967        let buffer = MultiBuffer::build_simple(
  968            &"
  969                class Foo:
  970                    # Hello!
  971
  972                    def a():
  973                        print(1)
  974
  975                    def b():
  976                        print(2)
  977
  978                    def c():
  979                        print(3)
  980            "
  981            .unindent(),
  982            cx,
  983        );
  984        build_editor(buffer.clone(), window, cx)
  985    });
  986
  987    _ = editor.update(cx, |editor, window, cx| {
  988        editor.change_selections(None, window, cx, |s| {
  989            s.select_display_ranges([
  990                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  991            ]);
  992        });
  993        editor.fold(&Fold, window, cx);
  994        assert_eq!(
  995            editor.display_text(cx),
  996            "
  997                class Foo:
  998                    # Hello!
  999
 1000                    def a():
 1001                        print(1)
 1002
 1003                    def b():⋯
 1004
 1005                    def c():⋯
 1006            "
 1007            .unindent(),
 1008        );
 1009
 1010        editor.fold(&Fold, window, cx);
 1011        assert_eq!(
 1012            editor.display_text(cx),
 1013            "
 1014                class Foo:⋯
 1015            "
 1016            .unindent(),
 1017        );
 1018
 1019        editor.unfold_lines(&UnfoldLines, window, cx);
 1020        assert_eq!(
 1021            editor.display_text(cx),
 1022            "
 1023                class Foo:
 1024                    # Hello!
 1025
 1026                    def a():
 1027                        print(1)
 1028
 1029                    def b():⋯
 1030
 1031                    def c():⋯
 1032            "
 1033            .unindent(),
 1034        );
 1035
 1036        editor.unfold_lines(&UnfoldLines, window, cx);
 1037        assert_eq!(
 1038            editor.display_text(cx),
 1039            editor.buffer.read(cx).read(cx).text()
 1040        );
 1041    });
 1042}
 1043
 1044#[gpui::test]
 1045fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1046    init_test(cx, |_| {});
 1047
 1048    let editor = cx.add_window(|window, cx| {
 1049        let buffer = MultiBuffer::build_simple(
 1050            &"
 1051                class Foo:
 1052                    # Hello!
 1053
 1054                    def a():
 1055                        print(1)
 1056
 1057                    def b():
 1058                        print(2)
 1059
 1060
 1061                    def c():
 1062                        print(3)
 1063
 1064
 1065            "
 1066            .unindent(),
 1067            cx,
 1068        );
 1069        build_editor(buffer.clone(), window, cx)
 1070    });
 1071
 1072    _ = editor.update(cx, |editor, window, cx| {
 1073        editor.change_selections(None, window, cx, |s| {
 1074            s.select_display_ranges([
 1075                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1076            ]);
 1077        });
 1078        editor.fold(&Fold, window, cx);
 1079        assert_eq!(
 1080            editor.display_text(cx),
 1081            "
 1082                class Foo:
 1083                    # Hello!
 1084
 1085                    def a():
 1086                        print(1)
 1087
 1088                    def b():⋯
 1089
 1090
 1091                    def c():⋯
 1092
 1093
 1094            "
 1095            .unindent(),
 1096        );
 1097
 1098        editor.fold(&Fold, window, cx);
 1099        assert_eq!(
 1100            editor.display_text(cx),
 1101            "
 1102                class Foo:⋯
 1103
 1104
 1105            "
 1106            .unindent(),
 1107        );
 1108
 1109        editor.unfold_lines(&UnfoldLines, window, cx);
 1110        assert_eq!(
 1111            editor.display_text(cx),
 1112            "
 1113                class Foo:
 1114                    # Hello!
 1115
 1116                    def a():
 1117                        print(1)
 1118
 1119                    def b():⋯
 1120
 1121
 1122                    def c():⋯
 1123
 1124
 1125            "
 1126            .unindent(),
 1127        );
 1128
 1129        editor.unfold_lines(&UnfoldLines, window, cx);
 1130        assert_eq!(
 1131            editor.display_text(cx),
 1132            editor.buffer.read(cx).read(cx).text()
 1133        );
 1134    });
 1135}
 1136
 1137#[gpui::test]
 1138fn test_fold_at_level(cx: &mut TestAppContext) {
 1139    init_test(cx, |_| {});
 1140
 1141    let editor = cx.add_window(|window, cx| {
 1142        let buffer = MultiBuffer::build_simple(
 1143            &"
 1144                class Foo:
 1145                    # Hello!
 1146
 1147                    def a():
 1148                        print(1)
 1149
 1150                    def b():
 1151                        print(2)
 1152
 1153
 1154                class Bar:
 1155                    # World!
 1156
 1157                    def a():
 1158                        print(1)
 1159
 1160                    def b():
 1161                        print(2)
 1162
 1163
 1164            "
 1165            .unindent(),
 1166            cx,
 1167        );
 1168        build_editor(buffer.clone(), window, cx)
 1169    });
 1170
 1171    _ = editor.update(cx, |editor, window, cx| {
 1172        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1173        assert_eq!(
 1174            editor.display_text(cx),
 1175            "
 1176                class Foo:
 1177                    # Hello!
 1178
 1179                    def a():⋯
 1180
 1181                    def b():⋯
 1182
 1183
 1184                class Bar:
 1185                    # World!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192            "
 1193            .unindent(),
 1194        );
 1195
 1196        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1197        assert_eq!(
 1198            editor.display_text(cx),
 1199            "
 1200                class Foo:⋯
 1201
 1202
 1203                class Bar:⋯
 1204
 1205
 1206            "
 1207            .unindent(),
 1208        );
 1209
 1210        editor.unfold_all(&UnfoldAll, window, cx);
 1211        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1212        assert_eq!(
 1213            editor.display_text(cx),
 1214            "
 1215                class Foo:
 1216                    # Hello!
 1217
 1218                    def a():
 1219                        print(1)
 1220
 1221                    def b():
 1222                        print(2)
 1223
 1224
 1225                class Bar:
 1226                    # World!
 1227
 1228                    def a():
 1229                        print(1)
 1230
 1231                    def b():
 1232                        print(2)
 1233
 1234
 1235            "
 1236            .unindent(),
 1237        );
 1238
 1239        assert_eq!(
 1240            editor.display_text(cx),
 1241            editor.buffer.read(cx).read(cx).text()
 1242        );
 1243    });
 1244}
 1245
 1246#[gpui::test]
 1247fn test_move_cursor(cx: &mut TestAppContext) {
 1248    init_test(cx, |_| {});
 1249
 1250    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1251    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1252
 1253    buffer.update(cx, |buffer, cx| {
 1254        buffer.edit(
 1255            vec![
 1256                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1257                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1258            ],
 1259            None,
 1260            cx,
 1261        );
 1262    });
 1263    _ = editor.update(cx, |editor, window, cx| {
 1264        assert_eq!(
 1265            editor.selections.display_ranges(cx),
 1266            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1267        );
 1268
 1269        editor.move_down(&MoveDown, window, cx);
 1270        assert_eq!(
 1271            editor.selections.display_ranges(cx),
 1272            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1273        );
 1274
 1275        editor.move_right(&MoveRight, window, cx);
 1276        assert_eq!(
 1277            editor.selections.display_ranges(cx),
 1278            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1279        );
 1280
 1281        editor.move_left(&MoveLeft, window, cx);
 1282        assert_eq!(
 1283            editor.selections.display_ranges(cx),
 1284            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1285        );
 1286
 1287        editor.move_up(&MoveUp, window, cx);
 1288        assert_eq!(
 1289            editor.selections.display_ranges(cx),
 1290            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1291        );
 1292
 1293        editor.move_to_end(&MoveToEnd, window, cx);
 1294        assert_eq!(
 1295            editor.selections.display_ranges(cx),
 1296            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1297        );
 1298
 1299        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1300        assert_eq!(
 1301            editor.selections.display_ranges(cx),
 1302            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1303        );
 1304
 1305        editor.change_selections(None, window, cx, |s| {
 1306            s.select_display_ranges([
 1307                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1308            ]);
 1309        });
 1310        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1311        assert_eq!(
 1312            editor.selections.display_ranges(cx),
 1313            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1314        );
 1315
 1316        editor.select_to_end(&SelectToEnd, window, cx);
 1317        assert_eq!(
 1318            editor.selections.display_ranges(cx),
 1319            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1320        );
 1321    });
 1322}
 1323
 1324#[gpui::test]
 1325fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1326    init_test(cx, |_| {});
 1327
 1328    let editor = cx.add_window(|window, cx| {
 1329        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1330        build_editor(buffer.clone(), window, cx)
 1331    });
 1332
 1333    assert_eq!('🟥'.len_utf8(), 4);
 1334    assert_eq!('α'.len_utf8(), 2);
 1335
 1336    _ = editor.update(cx, |editor, window, cx| {
 1337        editor.fold_creases(
 1338            vec![
 1339                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1340                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1341                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1342            ],
 1343            true,
 1344            window,
 1345            cx,
 1346        );
 1347        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1348
 1349        editor.move_right(&MoveRight, window, cx);
 1350        assert_eq!(
 1351            editor.selections.display_ranges(cx),
 1352            &[empty_range(0, "🟥".len())]
 1353        );
 1354        editor.move_right(&MoveRight, window, cx);
 1355        assert_eq!(
 1356            editor.selections.display_ranges(cx),
 1357            &[empty_range(0, "🟥🟧".len())]
 1358        );
 1359        editor.move_right(&MoveRight, window, cx);
 1360        assert_eq!(
 1361            editor.selections.display_ranges(cx),
 1362            &[empty_range(0, "🟥🟧⋯".len())]
 1363        );
 1364
 1365        editor.move_down(&MoveDown, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[empty_range(1, "ab⋯e".len())]
 1369        );
 1370        editor.move_left(&MoveLeft, window, cx);
 1371        assert_eq!(
 1372            editor.selections.display_ranges(cx),
 1373            &[empty_range(1, "ab⋯".len())]
 1374        );
 1375        editor.move_left(&MoveLeft, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(1, "ab".len())]
 1379        );
 1380        editor.move_left(&MoveLeft, window, cx);
 1381        assert_eq!(
 1382            editor.selections.display_ranges(cx),
 1383            &[empty_range(1, "a".len())]
 1384        );
 1385
 1386        editor.move_down(&MoveDown, window, cx);
 1387        assert_eq!(
 1388            editor.selections.display_ranges(cx),
 1389            &[empty_range(2, "α".len())]
 1390        );
 1391        editor.move_right(&MoveRight, window, cx);
 1392        assert_eq!(
 1393            editor.selections.display_ranges(cx),
 1394            &[empty_range(2, "αβ".len())]
 1395        );
 1396        editor.move_right(&MoveRight, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(2, "αβ⋯".len())]
 1400        );
 1401        editor.move_right(&MoveRight, window, cx);
 1402        assert_eq!(
 1403            editor.selections.display_ranges(cx),
 1404            &[empty_range(2, "αβ⋯ε".len())]
 1405        );
 1406
 1407        editor.move_up(&MoveUp, window, cx);
 1408        assert_eq!(
 1409            editor.selections.display_ranges(cx),
 1410            &[empty_range(1, "ab⋯e".len())]
 1411        );
 1412        editor.move_down(&MoveDown, window, cx);
 1413        assert_eq!(
 1414            editor.selections.display_ranges(cx),
 1415            &[empty_range(2, "αβ⋯ε".len())]
 1416        );
 1417        editor.move_up(&MoveUp, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(1, "ab⋯e".len())]
 1421        );
 1422
 1423        editor.move_up(&MoveUp, window, cx);
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[empty_range(0, "🟥🟧".len())]
 1427        );
 1428        editor.move_left(&MoveLeft, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[empty_range(0, "🟥".len())]
 1432        );
 1433        editor.move_left(&MoveLeft, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(0, "".len())]
 1437        );
 1438    });
 1439}
 1440
 1441#[gpui::test]
 1442fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1443    init_test(cx, |_| {});
 1444
 1445    let editor = cx.add_window(|window, cx| {
 1446        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1447        build_editor(buffer.clone(), window, cx)
 1448    });
 1449    _ = editor.update(cx, |editor, window, cx| {
 1450        editor.change_selections(None, window, cx, |s| {
 1451            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1452        });
 1453
 1454        // moving above start of document should move selection to start of document,
 1455        // but the next move down should still be at the original goal_x
 1456        editor.move_up(&MoveUp, window, cx);
 1457        assert_eq!(
 1458            editor.selections.display_ranges(cx),
 1459            &[empty_range(0, "".len())]
 1460        );
 1461
 1462        editor.move_down(&MoveDown, window, cx);
 1463        assert_eq!(
 1464            editor.selections.display_ranges(cx),
 1465            &[empty_range(1, "abcd".len())]
 1466        );
 1467
 1468        editor.move_down(&MoveDown, window, cx);
 1469        assert_eq!(
 1470            editor.selections.display_ranges(cx),
 1471            &[empty_range(2, "αβγ".len())]
 1472        );
 1473
 1474        editor.move_down(&MoveDown, window, cx);
 1475        assert_eq!(
 1476            editor.selections.display_ranges(cx),
 1477            &[empty_range(3, "abcd".len())]
 1478        );
 1479
 1480        editor.move_down(&MoveDown, window, cx);
 1481        assert_eq!(
 1482            editor.selections.display_ranges(cx),
 1483            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1484        );
 1485
 1486        // moving past end of document should not change goal_x
 1487        editor.move_down(&MoveDown, window, cx);
 1488        assert_eq!(
 1489            editor.selections.display_ranges(cx),
 1490            &[empty_range(5, "".len())]
 1491        );
 1492
 1493        editor.move_down(&MoveDown, window, cx);
 1494        assert_eq!(
 1495            editor.selections.display_ranges(cx),
 1496            &[empty_range(5, "".len())]
 1497        );
 1498
 1499        editor.move_up(&MoveUp, window, cx);
 1500        assert_eq!(
 1501            editor.selections.display_ranges(cx),
 1502            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1503        );
 1504
 1505        editor.move_up(&MoveUp, window, cx);
 1506        assert_eq!(
 1507            editor.selections.display_ranges(cx),
 1508            &[empty_range(3, "abcd".len())]
 1509        );
 1510
 1511        editor.move_up(&MoveUp, window, cx);
 1512        assert_eq!(
 1513            editor.selections.display_ranges(cx),
 1514            &[empty_range(2, "αβγ".len())]
 1515        );
 1516    });
 1517}
 1518
 1519#[gpui::test]
 1520fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1521    init_test(cx, |_| {});
 1522    let move_to_beg = MoveToBeginningOfLine {
 1523        stop_at_soft_wraps: true,
 1524        stop_at_indent: true,
 1525    };
 1526
 1527    let delete_to_beg = DeleteToBeginningOfLine {
 1528        stop_at_indent: false,
 1529    };
 1530
 1531    let move_to_end = MoveToEndOfLine {
 1532        stop_at_soft_wraps: true,
 1533    };
 1534
 1535    let editor = cx.add_window(|window, cx| {
 1536        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1537        build_editor(buffer, window, cx)
 1538    });
 1539    _ = editor.update(cx, |editor, window, cx| {
 1540        editor.change_selections(None, window, cx, |s| {
 1541            s.select_display_ranges([
 1542                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1543                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1544            ]);
 1545        });
 1546    });
 1547
 1548    _ = editor.update(cx, |editor, window, cx| {
 1549        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1550        assert_eq!(
 1551            editor.selections.display_ranges(cx),
 1552            &[
 1553                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1554                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1555            ]
 1556        );
 1557    });
 1558
 1559    _ = editor.update(cx, |editor, window, cx| {
 1560        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[
 1564                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1565                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1566            ]
 1567        );
 1568    });
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[
 1575                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1576                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1577            ]
 1578        );
 1579    });
 1580
 1581    _ = editor.update(cx, |editor, window, cx| {
 1582        editor.move_to_end_of_line(&move_to_end, window, cx);
 1583        assert_eq!(
 1584            editor.selections.display_ranges(cx),
 1585            &[
 1586                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1587                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1588            ]
 1589        );
 1590    });
 1591
 1592    // Moving to the end of line again is a no-op.
 1593    _ = editor.update(cx, |editor, window, cx| {
 1594        editor.move_to_end_of_line(&move_to_end, window, cx);
 1595        assert_eq!(
 1596            editor.selections.display_ranges(cx),
 1597            &[
 1598                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1599                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1600            ]
 1601        );
 1602    });
 1603
 1604    _ = editor.update(cx, |editor, window, cx| {
 1605        editor.move_left(&MoveLeft, window, cx);
 1606        editor.select_to_beginning_of_line(
 1607            &SelectToBeginningOfLine {
 1608                stop_at_soft_wraps: true,
 1609                stop_at_indent: true,
 1610            },
 1611            window,
 1612            cx,
 1613        );
 1614        assert_eq!(
 1615            editor.selections.display_ranges(cx),
 1616            &[
 1617                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1618                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1619            ]
 1620        );
 1621    });
 1622
 1623    _ = editor.update(cx, |editor, window, cx| {
 1624        editor.select_to_beginning_of_line(
 1625            &SelectToBeginningOfLine {
 1626                stop_at_soft_wraps: true,
 1627                stop_at_indent: true,
 1628            },
 1629            window,
 1630            cx,
 1631        );
 1632        assert_eq!(
 1633            editor.selections.display_ranges(cx),
 1634            &[
 1635                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1636                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1637            ]
 1638        );
 1639    });
 1640
 1641    _ = editor.update(cx, |editor, window, cx| {
 1642        editor.select_to_beginning_of_line(
 1643            &SelectToBeginningOfLine {
 1644                stop_at_soft_wraps: true,
 1645                stop_at_indent: true,
 1646            },
 1647            window,
 1648            cx,
 1649        );
 1650        assert_eq!(
 1651            editor.selections.display_ranges(cx),
 1652            &[
 1653                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1654                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1655            ]
 1656        );
 1657    });
 1658
 1659    _ = editor.update(cx, |editor, window, cx| {
 1660        editor.select_to_end_of_line(
 1661            &SelectToEndOfLine {
 1662                stop_at_soft_wraps: true,
 1663            },
 1664            window,
 1665            cx,
 1666        );
 1667        assert_eq!(
 1668            editor.selections.display_ranges(cx),
 1669            &[
 1670                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1671                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1672            ]
 1673        );
 1674    });
 1675
 1676    _ = editor.update(cx, |editor, window, cx| {
 1677        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1678        assert_eq!(editor.display_text(cx), "ab\n  de");
 1679        assert_eq!(
 1680            editor.selections.display_ranges(cx),
 1681            &[
 1682                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1683                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1684            ]
 1685        );
 1686    });
 1687
 1688    _ = editor.update(cx, |editor, window, cx| {
 1689        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1690        assert_eq!(editor.display_text(cx), "\n");
 1691        assert_eq!(
 1692            editor.selections.display_ranges(cx),
 1693            &[
 1694                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1695                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1696            ]
 1697        );
 1698    });
 1699}
 1700
 1701#[gpui::test]
 1702fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1703    init_test(cx, |_| {});
 1704    let move_to_beg = MoveToBeginningOfLine {
 1705        stop_at_soft_wraps: false,
 1706        stop_at_indent: false,
 1707    };
 1708
 1709    let move_to_end = MoveToEndOfLine {
 1710        stop_at_soft_wraps: false,
 1711    };
 1712
 1713    let editor = cx.add_window(|window, cx| {
 1714        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1715        build_editor(buffer, window, cx)
 1716    });
 1717
 1718    _ = editor.update(cx, |editor, window, cx| {
 1719        editor.set_wrap_width(Some(140.0.into()), cx);
 1720
 1721        // We expect the following lines after wrapping
 1722        // ```
 1723        // thequickbrownfox
 1724        // jumpedoverthelazydo
 1725        // gs
 1726        // ```
 1727        // The final `gs` was soft-wrapped onto a new line.
 1728        assert_eq!(
 1729            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1730            editor.display_text(cx),
 1731        );
 1732
 1733        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1734        // Start the cursor at the `k` on the first line
 1735        editor.change_selections(None, window, cx, |s| {
 1736            s.select_display_ranges([
 1737                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1738            ]);
 1739        });
 1740
 1741        // Moving to the beginning of the line should put us at the beginning of the line.
 1742        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1743        assert_eq!(
 1744            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1745            editor.selections.display_ranges(cx)
 1746        );
 1747
 1748        // Moving to the end of the line should put us at the end of the line.
 1749        editor.move_to_end_of_line(&move_to_end, window, cx);
 1750        assert_eq!(
 1751            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1752            editor.selections.display_ranges(cx)
 1753        );
 1754
 1755        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1756        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1757        editor.change_selections(None, window, cx, |s| {
 1758            s.select_display_ranges([
 1759                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1760            ]);
 1761        });
 1762
 1763        // Moving to the beginning of the line should put us at the start of the second line of
 1764        // display text, i.e., the `j`.
 1765        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1766        assert_eq!(
 1767            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1768            editor.selections.display_ranges(cx)
 1769        );
 1770
 1771        // Moving to the beginning of the line again should be a no-op.
 1772        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1773        assert_eq!(
 1774            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1775            editor.selections.display_ranges(cx)
 1776        );
 1777
 1778        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1779        // next display line.
 1780        editor.move_to_end_of_line(&move_to_end, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line again should be a no-op.
 1787        editor.move_to_end_of_line(&move_to_end, window, cx);
 1788        assert_eq!(
 1789            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1790            editor.selections.display_ranges(cx)
 1791        );
 1792    });
 1793}
 1794
 1795#[gpui::test]
 1796fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1797    init_test(cx, |_| {});
 1798
 1799    let move_to_beg = MoveToBeginningOfLine {
 1800        stop_at_soft_wraps: true,
 1801        stop_at_indent: true,
 1802    };
 1803
 1804    let select_to_beg = SelectToBeginningOfLine {
 1805        stop_at_soft_wraps: true,
 1806        stop_at_indent: true,
 1807    };
 1808
 1809    let delete_to_beg = DeleteToBeginningOfLine {
 1810        stop_at_indent: true,
 1811    };
 1812
 1813    let move_to_end = MoveToEndOfLine {
 1814        stop_at_soft_wraps: false,
 1815    };
 1816
 1817    let editor = cx.add_window(|window, cx| {
 1818        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1819        build_editor(buffer, window, cx)
 1820    });
 1821
 1822    _ = editor.update(cx, |editor, window, cx| {
 1823        editor.change_selections(None, window, cx, |s| {
 1824            s.select_display_ranges([
 1825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1826                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1827            ]);
 1828        });
 1829
 1830        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1831        // and the second cursor at the first non-whitespace character in the line.
 1832        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1833        assert_eq!(
 1834            editor.selections.display_ranges(cx),
 1835            &[
 1836                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1837                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1838            ]
 1839        );
 1840
 1841        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1842        // and should move the second cursor to the beginning of the line.
 1843        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1844        assert_eq!(
 1845            editor.selections.display_ranges(cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1849            ]
 1850        );
 1851
 1852        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1853        // and should move the second cursor back to the first non-whitespace character in the line.
 1854        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1855        assert_eq!(
 1856            editor.selections.display_ranges(cx),
 1857            &[
 1858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1859                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1860            ]
 1861        );
 1862
 1863        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1864        // and to the first non-whitespace character in the line for the second cursor.
 1865        editor.move_to_end_of_line(&move_to_end, window, cx);
 1866        editor.move_left(&MoveLeft, window, cx);
 1867        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1868        assert_eq!(
 1869            editor.selections.display_ranges(cx),
 1870            &[
 1871                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1872                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1873            ]
 1874        );
 1875
 1876        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1877        // and should select to the beginning of the line for the second cursor.
 1878        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1879        assert_eq!(
 1880            editor.selections.display_ranges(cx),
 1881            &[
 1882                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1883                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1884            ]
 1885        );
 1886
 1887        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1888        // and should delete to the first non-whitespace character in the line for the second cursor.
 1889        editor.move_to_end_of_line(&move_to_end, window, cx);
 1890        editor.move_left(&MoveLeft, window, cx);
 1891        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1892        assert_eq!(editor.text(cx), "c\n  f");
 1893    });
 1894}
 1895
 1896#[gpui::test]
 1897fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1898    init_test(cx, |_| {});
 1899
 1900    let editor = cx.add_window(|window, cx| {
 1901        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1902        build_editor(buffer, window, cx)
 1903    });
 1904    _ = editor.update(cx, |editor, window, cx| {
 1905        editor.change_selections(None, window, cx, |s| {
 1906            s.select_display_ranges([
 1907                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1908                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1909            ])
 1910        });
 1911        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1912        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1913
 1914        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1915        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1916
 1917        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1918        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1919
 1920        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1921        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1922
 1923        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1924        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1925
 1926        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1927        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1928
 1929        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1930        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1931
 1932        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1933        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1934
 1935        editor.move_right(&MoveRight, window, cx);
 1936        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1937        assert_selection_ranges(
 1938            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1939            editor,
 1940            cx,
 1941        );
 1942
 1943        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1944        assert_selection_ranges(
 1945            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1946            editor,
 1947            cx,
 1948        );
 1949
 1950        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1951        assert_selection_ranges(
 1952            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1953            editor,
 1954            cx,
 1955        );
 1956    });
 1957}
 1958
 1959#[gpui::test]
 1960fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1961    init_test(cx, |_| {});
 1962
 1963    let editor = cx.add_window(|window, cx| {
 1964        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1965        build_editor(buffer, window, cx)
 1966    });
 1967
 1968    _ = editor.update(cx, |editor, window, cx| {
 1969        editor.set_wrap_width(Some(140.0.into()), cx);
 1970        assert_eq!(
 1971            editor.display_text(cx),
 1972            "use one::{\n    two::three::\n    four::five\n};"
 1973        );
 1974
 1975        editor.change_selections(None, window, cx, |s| {
 1976            s.select_display_ranges([
 1977                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1978            ]);
 1979        });
 1980
 1981        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1982        assert_eq!(
 1983            editor.selections.display_ranges(cx),
 1984            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1985        );
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_eq!(
 1989            editor.selections.display_ranges(cx),
 1990            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1991        );
 1992
 1993        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1994        assert_eq!(
 1995            editor.selections.display_ranges(cx),
 1996            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1997        );
 1998
 1999        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2000        assert_eq!(
 2001            editor.selections.display_ranges(cx),
 2002            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2003        );
 2004
 2005        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2006        assert_eq!(
 2007            editor.selections.display_ranges(cx),
 2008            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2009        );
 2010
 2011        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2012        assert_eq!(
 2013            editor.selections.display_ranges(cx),
 2014            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2015        );
 2016    });
 2017}
 2018
 2019#[gpui::test]
 2020async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2021    init_test(cx, |_| {});
 2022    let mut cx = EditorTestContext::new(cx).await;
 2023
 2024    let line_height = cx.editor(|editor, window, _| {
 2025        editor
 2026            .style()
 2027            .unwrap()
 2028            .text
 2029            .line_height_in_pixels(window.rem_size())
 2030    });
 2031    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2032
 2033    cx.set_state(
 2034        &r#"ˇone
 2035        two
 2036
 2037        three
 2038        fourˇ
 2039        five
 2040
 2041        six"#
 2042            .unindent(),
 2043    );
 2044
 2045    cx.update_editor(|editor, window, cx| {
 2046        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2047    });
 2048    cx.assert_editor_state(
 2049        &r#"one
 2050        two
 2051        ˇ
 2052        three
 2053        four
 2054        five
 2055        ˇ
 2056        six"#
 2057            .unindent(),
 2058    );
 2059
 2060    cx.update_editor(|editor, window, cx| {
 2061        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2062    });
 2063    cx.assert_editor_state(
 2064        &r#"one
 2065        two
 2066
 2067        three
 2068        four
 2069        five
 2070        ˇ
 2071        sixˇ"#
 2072            .unindent(),
 2073    );
 2074
 2075    cx.update_editor(|editor, window, cx| {
 2076        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2077    });
 2078    cx.assert_editor_state(
 2079        &r#"one
 2080        two
 2081
 2082        three
 2083        four
 2084        five
 2085
 2086        sixˇ"#
 2087            .unindent(),
 2088    );
 2089
 2090    cx.update_editor(|editor, window, cx| {
 2091        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2092    });
 2093    cx.assert_editor_state(
 2094        &r#"one
 2095        two
 2096
 2097        three
 2098        four
 2099        five
 2100        ˇ
 2101        six"#
 2102            .unindent(),
 2103    );
 2104
 2105    cx.update_editor(|editor, window, cx| {
 2106        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2107    });
 2108    cx.assert_editor_state(
 2109        &r#"one
 2110        two
 2111        ˇ
 2112        three
 2113        four
 2114        five
 2115
 2116        six"#
 2117            .unindent(),
 2118    );
 2119
 2120    cx.update_editor(|editor, window, cx| {
 2121        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2122    });
 2123    cx.assert_editor_state(
 2124        &r#"ˇone
 2125        two
 2126
 2127        three
 2128        four
 2129        five
 2130
 2131        six"#
 2132            .unindent(),
 2133    );
 2134}
 2135
 2136#[gpui::test]
 2137async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2138    init_test(cx, |_| {});
 2139    let mut cx = EditorTestContext::new(cx).await;
 2140    let line_height = cx.editor(|editor, window, _| {
 2141        editor
 2142            .style()
 2143            .unwrap()
 2144            .text
 2145            .line_height_in_pixels(window.rem_size())
 2146    });
 2147    let window = cx.window;
 2148    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2149
 2150    cx.set_state(
 2151        r#"ˇone
 2152        two
 2153        three
 2154        four
 2155        five
 2156        six
 2157        seven
 2158        eight
 2159        nine
 2160        ten
 2161        "#,
 2162    );
 2163
 2164    cx.update_editor(|editor, window, cx| {
 2165        assert_eq!(
 2166            editor.snapshot(window, cx).scroll_position(),
 2167            gpui::Point::new(0., 0.)
 2168        );
 2169        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2170        assert_eq!(
 2171            editor.snapshot(window, cx).scroll_position(),
 2172            gpui::Point::new(0., 3.)
 2173        );
 2174        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2175        assert_eq!(
 2176            editor.snapshot(window, cx).scroll_position(),
 2177            gpui::Point::new(0., 6.)
 2178        );
 2179        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2180        assert_eq!(
 2181            editor.snapshot(window, cx).scroll_position(),
 2182            gpui::Point::new(0., 3.)
 2183        );
 2184
 2185        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2186        assert_eq!(
 2187            editor.snapshot(window, cx).scroll_position(),
 2188            gpui::Point::new(0., 1.)
 2189        );
 2190        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2191        assert_eq!(
 2192            editor.snapshot(window, cx).scroll_position(),
 2193            gpui::Point::new(0., 3.)
 2194        );
 2195    });
 2196}
 2197
 2198#[gpui::test]
 2199async fn test_autoscroll(cx: &mut TestAppContext) {
 2200    init_test(cx, |_| {});
 2201    let mut cx = EditorTestContext::new(cx).await;
 2202
 2203    let line_height = cx.update_editor(|editor, window, cx| {
 2204        editor.set_vertical_scroll_margin(2, cx);
 2205        editor
 2206            .style()
 2207            .unwrap()
 2208            .text
 2209            .line_height_in_pixels(window.rem_size())
 2210    });
 2211    let window = cx.window;
 2212    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2213
 2214    cx.set_state(
 2215        r#"ˇone
 2216            two
 2217            three
 2218            four
 2219            five
 2220            six
 2221            seven
 2222            eight
 2223            nine
 2224            ten
 2225        "#,
 2226    );
 2227    cx.update_editor(|editor, window, cx| {
 2228        assert_eq!(
 2229            editor.snapshot(window, cx).scroll_position(),
 2230            gpui::Point::new(0., 0.0)
 2231        );
 2232    });
 2233
 2234    // Add a cursor below the visible area. Since both cursors cannot fit
 2235    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2236    // allows the vertical scroll margin below that cursor.
 2237    cx.update_editor(|editor, window, cx| {
 2238        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2239            selections.select_ranges([
 2240                Point::new(0, 0)..Point::new(0, 0),
 2241                Point::new(6, 0)..Point::new(6, 0),
 2242            ]);
 2243        })
 2244    });
 2245    cx.update_editor(|editor, window, cx| {
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 3.0)
 2249        );
 2250    });
 2251
 2252    // Move down. The editor cursor scrolls down to track the newest cursor.
 2253    cx.update_editor(|editor, window, cx| {
 2254        editor.move_down(&Default::default(), window, cx);
 2255    });
 2256    cx.update_editor(|editor, window, cx| {
 2257        assert_eq!(
 2258            editor.snapshot(window, cx).scroll_position(),
 2259            gpui::Point::new(0., 4.0)
 2260        );
 2261    });
 2262
 2263    // Add a cursor above the visible area. Since both cursors fit on screen,
 2264    // the editor scrolls to show both.
 2265    cx.update_editor(|editor, window, cx| {
 2266        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2267            selections.select_ranges([
 2268                Point::new(1, 0)..Point::new(1, 0),
 2269                Point::new(6, 0)..Point::new(6, 0),
 2270            ]);
 2271        })
 2272    });
 2273    cx.update_editor(|editor, window, cx| {
 2274        assert_eq!(
 2275            editor.snapshot(window, cx).scroll_position(),
 2276            gpui::Point::new(0., 1.0)
 2277        );
 2278    });
 2279}
 2280
 2281#[gpui::test]
 2282async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2283    init_test(cx, |_| {});
 2284    let mut cx = EditorTestContext::new(cx).await;
 2285
 2286    let line_height = cx.editor(|editor, window, _cx| {
 2287        editor
 2288            .style()
 2289            .unwrap()
 2290            .text
 2291            .line_height_in_pixels(window.rem_size())
 2292    });
 2293    let window = cx.window;
 2294    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2295    cx.set_state(
 2296        &r#"
 2297        ˇone
 2298        two
 2299        threeˇ
 2300        four
 2301        five
 2302        six
 2303        seven
 2304        eight
 2305        nine
 2306        ten
 2307        "#
 2308        .unindent(),
 2309    );
 2310
 2311    cx.update_editor(|editor, window, cx| {
 2312        editor.move_page_down(&MovePageDown::default(), window, cx)
 2313    });
 2314    cx.assert_editor_state(
 2315        &r#"
 2316        one
 2317        two
 2318        three
 2319        ˇfour
 2320        five
 2321        sixˇ
 2322        seven
 2323        eight
 2324        nine
 2325        ten
 2326        "#
 2327        .unindent(),
 2328    );
 2329
 2330    cx.update_editor(|editor, window, cx| {
 2331        editor.move_page_down(&MovePageDown::default(), window, cx)
 2332    });
 2333    cx.assert_editor_state(
 2334        &r#"
 2335        one
 2336        two
 2337        three
 2338        four
 2339        five
 2340        six
 2341        ˇseven
 2342        eight
 2343        nineˇ
 2344        ten
 2345        "#
 2346        .unindent(),
 2347    );
 2348
 2349    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2350    cx.assert_editor_state(
 2351        &r#"
 2352        one
 2353        two
 2354        three
 2355        ˇfour
 2356        five
 2357        sixˇ
 2358        seven
 2359        eight
 2360        nine
 2361        ten
 2362        "#
 2363        .unindent(),
 2364    );
 2365
 2366    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2367    cx.assert_editor_state(
 2368        &r#"
 2369        ˇone
 2370        two
 2371        threeˇ
 2372        four
 2373        five
 2374        six
 2375        seven
 2376        eight
 2377        nine
 2378        ten
 2379        "#
 2380        .unindent(),
 2381    );
 2382
 2383    // Test select collapsing
 2384    cx.update_editor(|editor, window, cx| {
 2385        editor.move_page_down(&MovePageDown::default(), window, cx);
 2386        editor.move_page_down(&MovePageDown::default(), window, cx);
 2387        editor.move_page_down(&MovePageDown::default(), window, cx);
 2388    });
 2389    cx.assert_editor_state(
 2390        &r#"
 2391        one
 2392        two
 2393        three
 2394        four
 2395        five
 2396        six
 2397        seven
 2398        eight
 2399        nine
 2400        ˇten
 2401        ˇ"#
 2402        .unindent(),
 2403    );
 2404}
 2405
 2406#[gpui::test]
 2407async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2408    init_test(cx, |_| {});
 2409    let mut cx = EditorTestContext::new(cx).await;
 2410    cx.set_state("one «two threeˇ» four");
 2411    cx.update_editor(|editor, window, cx| {
 2412        editor.delete_to_beginning_of_line(
 2413            &DeleteToBeginningOfLine {
 2414                stop_at_indent: false,
 2415            },
 2416            window,
 2417            cx,
 2418        );
 2419        assert_eq!(editor.text(cx), " four");
 2420    });
 2421}
 2422
 2423#[gpui::test]
 2424fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2425    init_test(cx, |_| {});
 2426
 2427    let editor = cx.add_window(|window, cx| {
 2428        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2429        build_editor(buffer.clone(), window, cx)
 2430    });
 2431
 2432    _ = editor.update(cx, |editor, window, cx| {
 2433        editor.change_selections(None, window, cx, |s| {
 2434            s.select_display_ranges([
 2435                // an empty selection - the preceding word fragment is deleted
 2436                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2437                // characters selected - they are deleted
 2438                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2439            ])
 2440        });
 2441        editor.delete_to_previous_word_start(
 2442            &DeleteToPreviousWordStart {
 2443                ignore_newlines: false,
 2444            },
 2445            window,
 2446            cx,
 2447        );
 2448        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2449    });
 2450
 2451    _ = editor.update(cx, |editor, window, cx| {
 2452        editor.change_selections(None, window, cx, |s| {
 2453            s.select_display_ranges([
 2454                // an empty selection - the following word fragment is deleted
 2455                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2456                // characters selected - they are deleted
 2457                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2458            ])
 2459        });
 2460        editor.delete_to_next_word_end(
 2461            &DeleteToNextWordEnd {
 2462                ignore_newlines: false,
 2463            },
 2464            window,
 2465            cx,
 2466        );
 2467        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2468    });
 2469}
 2470
 2471#[gpui::test]
 2472fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2473    init_test(cx, |_| {});
 2474
 2475    let editor = cx.add_window(|window, cx| {
 2476        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2477        build_editor(buffer.clone(), window, cx)
 2478    });
 2479    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2480        ignore_newlines: false,
 2481    };
 2482    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2483        ignore_newlines: true,
 2484    };
 2485
 2486    _ = editor.update(cx, |editor, window, cx| {
 2487        editor.change_selections(None, window, cx, |s| {
 2488            s.select_display_ranges([
 2489                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2490            ])
 2491        });
 2492        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2493        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2494        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2495        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2496        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2497        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2498        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2499        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2504    });
 2505}
 2506
 2507#[gpui::test]
 2508fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2509    init_test(cx, |_| {});
 2510
 2511    let editor = cx.add_window(|window, cx| {
 2512        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2513        build_editor(buffer.clone(), window, cx)
 2514    });
 2515    let del_to_next_word_end = DeleteToNextWordEnd {
 2516        ignore_newlines: false,
 2517    };
 2518    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2519        ignore_newlines: true,
 2520    };
 2521
 2522    _ = editor.update(cx, |editor, window, cx| {
 2523        editor.change_selections(None, window, cx, |s| {
 2524            s.select_display_ranges([
 2525                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2526            ])
 2527        });
 2528        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2529        assert_eq!(
 2530            editor.buffer.read(cx).read(cx).text(),
 2531            "one\n   two\nthree\n   four"
 2532        );
 2533        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2534        assert_eq!(
 2535            editor.buffer.read(cx).read(cx).text(),
 2536            "\n   two\nthree\n   four"
 2537        );
 2538        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2539        assert_eq!(
 2540            editor.buffer.read(cx).read(cx).text(),
 2541            "two\nthree\n   four"
 2542        );
 2543        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2544        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2545        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2547        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2549    });
 2550}
 2551
 2552#[gpui::test]
 2553fn test_newline(cx: &mut TestAppContext) {
 2554    init_test(cx, |_| {});
 2555
 2556    let editor = cx.add_window(|window, cx| {
 2557        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2558        build_editor(buffer.clone(), window, cx)
 2559    });
 2560
 2561    _ = editor.update(cx, |editor, window, cx| {
 2562        editor.change_selections(None, window, cx, |s| {
 2563            s.select_display_ranges([
 2564                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2565                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2566                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2567            ])
 2568        });
 2569
 2570        editor.newline(&Newline, window, cx);
 2571        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2572    });
 2573}
 2574
 2575#[gpui::test]
 2576fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2577    init_test(cx, |_| {});
 2578
 2579    let editor = cx.add_window(|window, cx| {
 2580        let buffer = MultiBuffer::build_simple(
 2581            "
 2582                a
 2583                b(
 2584                    X
 2585                )
 2586                c(
 2587                    X
 2588                )
 2589            "
 2590            .unindent()
 2591            .as_str(),
 2592            cx,
 2593        );
 2594        let mut editor = build_editor(buffer.clone(), window, cx);
 2595        editor.change_selections(None, window, cx, |s| {
 2596            s.select_ranges([
 2597                Point::new(2, 4)..Point::new(2, 5),
 2598                Point::new(5, 4)..Point::new(5, 5),
 2599            ])
 2600        });
 2601        editor
 2602    });
 2603
 2604    _ = editor.update(cx, |editor, window, cx| {
 2605        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2606        editor.buffer.update(cx, |buffer, cx| {
 2607            buffer.edit(
 2608                [
 2609                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2610                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2611                ],
 2612                None,
 2613                cx,
 2614            );
 2615            assert_eq!(
 2616                buffer.read(cx).text(),
 2617                "
 2618                    a
 2619                    b()
 2620                    c()
 2621                "
 2622                .unindent()
 2623            );
 2624        });
 2625        assert_eq!(
 2626            editor.selections.ranges(cx),
 2627            &[
 2628                Point::new(1, 2)..Point::new(1, 2),
 2629                Point::new(2, 2)..Point::new(2, 2),
 2630            ],
 2631        );
 2632
 2633        editor.newline(&Newline, window, cx);
 2634        assert_eq!(
 2635            editor.text(cx),
 2636            "
 2637                a
 2638                b(
 2639                )
 2640                c(
 2641                )
 2642            "
 2643            .unindent()
 2644        );
 2645
 2646        // The selections are moved after the inserted newlines
 2647        assert_eq!(
 2648            editor.selections.ranges(cx),
 2649            &[
 2650                Point::new(2, 0)..Point::new(2, 0),
 2651                Point::new(4, 0)..Point::new(4, 0),
 2652            ],
 2653        );
 2654    });
 2655}
 2656
 2657#[gpui::test]
 2658async fn test_newline_above(cx: &mut TestAppContext) {
 2659    init_test(cx, |settings| {
 2660        settings.defaults.tab_size = NonZeroU32::new(4)
 2661    });
 2662
 2663    let language = Arc::new(
 2664        Language::new(
 2665            LanguageConfig::default(),
 2666            Some(tree_sitter_rust::LANGUAGE.into()),
 2667        )
 2668        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2669        .unwrap(),
 2670    );
 2671
 2672    let mut cx = EditorTestContext::new(cx).await;
 2673    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2674    cx.set_state(indoc! {"
 2675        const a: ˇA = (
 2676 2677                «const_functionˇ»(ˇ),
 2678                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2679 2680        ˇ);ˇ
 2681    "});
 2682
 2683    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2684    cx.assert_editor_state(indoc! {"
 2685        ˇ
 2686        const a: A = (
 2687            ˇ
 2688            (
 2689                ˇ
 2690                ˇ
 2691                const_function(),
 2692                ˇ
 2693                ˇ
 2694                ˇ
 2695                ˇ
 2696                something_else,
 2697                ˇ
 2698            )
 2699            ˇ
 2700            ˇ
 2701        );
 2702    "});
 2703}
 2704
 2705#[gpui::test]
 2706async fn test_newline_below(cx: &mut TestAppContext) {
 2707    init_test(cx, |settings| {
 2708        settings.defaults.tab_size = NonZeroU32::new(4)
 2709    });
 2710
 2711    let language = Arc::new(
 2712        Language::new(
 2713            LanguageConfig::default(),
 2714            Some(tree_sitter_rust::LANGUAGE.into()),
 2715        )
 2716        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2717        .unwrap(),
 2718    );
 2719
 2720    let mut cx = EditorTestContext::new(cx).await;
 2721    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2722    cx.set_state(indoc! {"
 2723        const a: ˇA = (
 2724 2725                «const_functionˇ»(ˇ),
 2726                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2727 2728        ˇ);ˇ
 2729    "});
 2730
 2731    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2732    cx.assert_editor_state(indoc! {"
 2733        const a: A = (
 2734            ˇ
 2735            (
 2736                ˇ
 2737                const_function(),
 2738                ˇ
 2739                ˇ
 2740                something_else,
 2741                ˇ
 2742                ˇ
 2743                ˇ
 2744                ˇ
 2745            )
 2746            ˇ
 2747        );
 2748        ˇ
 2749        ˇ
 2750    "});
 2751}
 2752
 2753#[gpui::test]
 2754async fn test_newline_comments(cx: &mut TestAppContext) {
 2755    init_test(cx, |settings| {
 2756        settings.defaults.tab_size = NonZeroU32::new(4)
 2757    });
 2758
 2759    let language = Arc::new(Language::new(
 2760        LanguageConfig {
 2761            line_comments: vec!["// ".into()],
 2762            ..LanguageConfig::default()
 2763        },
 2764        None,
 2765    ));
 2766    {
 2767        let mut cx = EditorTestContext::new(cx).await;
 2768        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2769        cx.set_state(indoc! {"
 2770        // Fooˇ
 2771    "});
 2772
 2773        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2774        cx.assert_editor_state(indoc! {"
 2775        // Foo
 2776        // ˇ
 2777    "});
 2778        // Ensure that we add comment prefix when existing line contains space
 2779        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2780        cx.assert_editor_state(
 2781            indoc! {"
 2782        // Foo
 2783        //s
 2784        // ˇ
 2785    "}
 2786            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2787            .as_str(),
 2788        );
 2789        // Ensure that we add comment prefix when existing line does not contain space
 2790        cx.set_state(indoc! {"
 2791        // Foo
 2792        //ˇ
 2793    "});
 2794        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2795        cx.assert_editor_state(indoc! {"
 2796        // Foo
 2797        //
 2798        // ˇ
 2799    "});
 2800        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2801        cx.set_state(indoc! {"
 2802        ˇ// Foo
 2803    "});
 2804        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2805        cx.assert_editor_state(indoc! {"
 2806
 2807        ˇ// Foo
 2808    "});
 2809    }
 2810    // Ensure that comment continuations can be disabled.
 2811    update_test_language_settings(cx, |settings| {
 2812        settings.defaults.extend_comment_on_newline = Some(false);
 2813    });
 2814    let mut cx = EditorTestContext::new(cx).await;
 2815    cx.set_state(indoc! {"
 2816        // Fooˇ
 2817    "});
 2818    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2819    cx.assert_editor_state(indoc! {"
 2820        // Foo
 2821        ˇ
 2822    "});
 2823}
 2824
 2825#[gpui::test]
 2826async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2827    init_test(cx, |settings| {
 2828        settings.defaults.tab_size = NonZeroU32::new(4)
 2829    });
 2830
 2831    let language = Arc::new(Language::new(
 2832        LanguageConfig {
 2833            line_comments: vec!["// ".into(), "/// ".into()],
 2834            ..LanguageConfig::default()
 2835        },
 2836        None,
 2837    ));
 2838    {
 2839        let mut cx = EditorTestContext::new(cx).await;
 2840        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2841        cx.set_state(indoc! {"
 2842        //ˇ
 2843    "});
 2844        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2845        cx.assert_editor_state(indoc! {"
 2846        //
 2847        // ˇ
 2848    "});
 2849
 2850        cx.set_state(indoc! {"
 2851        ///ˇ
 2852    "});
 2853        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2854        cx.assert_editor_state(indoc! {"
 2855        ///
 2856        /// ˇ
 2857    "});
 2858    }
 2859}
 2860
 2861#[gpui::test]
 2862async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2863    init_test(cx, |settings| {
 2864        settings.defaults.tab_size = NonZeroU32::new(4)
 2865    });
 2866
 2867    let language = Arc::new(
 2868        Language::new(
 2869            LanguageConfig {
 2870                documentation: Some(language::DocumentationConfig {
 2871                    start: "/**".into(),
 2872                    end: "*/".into(),
 2873                    prefix: "* ".into(),
 2874                    tab_size: NonZeroU32::new(1).unwrap(),
 2875                }),
 2876
 2877                ..LanguageConfig::default()
 2878            },
 2879            Some(tree_sitter_rust::LANGUAGE.into()),
 2880        )
 2881        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2882        .unwrap(),
 2883    );
 2884
 2885    {
 2886        let mut cx = EditorTestContext::new(cx).await;
 2887        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2888        cx.set_state(indoc! {"
 2889        /**ˇ
 2890    "});
 2891
 2892        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2893        cx.assert_editor_state(indoc! {"
 2894        /**
 2895         * ˇ
 2896    "});
 2897        // Ensure that if cursor is before the comment start,
 2898        // we do not actually insert a comment prefix.
 2899        cx.set_state(indoc! {"
 2900        ˇ/**
 2901    "});
 2902        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2903        cx.assert_editor_state(indoc! {"
 2904
 2905        ˇ/**
 2906    "});
 2907        // Ensure that if cursor is between it doesn't add comment prefix.
 2908        cx.set_state(indoc! {"
 2909        /*ˇ*
 2910    "});
 2911        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2912        cx.assert_editor_state(indoc! {"
 2913        /*
 2914        ˇ*
 2915    "});
 2916        // Ensure that if suffix exists on same line after cursor it adds new line.
 2917        cx.set_state(indoc! {"
 2918        /**ˇ*/
 2919    "});
 2920        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2921        cx.assert_editor_state(indoc! {"
 2922        /**
 2923         * ˇ
 2924         */
 2925    "});
 2926        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2927        cx.set_state(indoc! {"
 2928        /**ˇ */
 2929    "});
 2930        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2931        cx.assert_editor_state(indoc! {"
 2932        /**
 2933         * ˇ
 2934         */
 2935    "});
 2936        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2937        cx.set_state(indoc! {"
 2938        /** ˇ*/
 2939    "});
 2940        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2941        cx.assert_editor_state(
 2942            indoc! {"
 2943        /**s
 2944         * ˇ
 2945         */
 2946    "}
 2947            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2948            .as_str(),
 2949        );
 2950        // Ensure that delimiter space is preserved when newline on already
 2951        // spaced delimiter.
 2952        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2953        cx.assert_editor_state(
 2954            indoc! {"
 2955        /**s
 2956         *s
 2957         * ˇ
 2958         */
 2959    "}
 2960            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2961            .as_str(),
 2962        );
 2963        // Ensure that delimiter space is preserved when space is not
 2964        // on existing delimiter.
 2965        cx.set_state(indoc! {"
 2966        /**
 2967 2968         */
 2969    "});
 2970        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2971        cx.assert_editor_state(indoc! {"
 2972        /**
 2973         *
 2974         * ˇ
 2975         */
 2976    "});
 2977        // Ensure that if suffix exists on same line after cursor it
 2978        // doesn't add extra new line if prefix is not on same line.
 2979        cx.set_state(indoc! {"
 2980        /**
 2981        ˇ*/
 2982    "});
 2983        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2984        cx.assert_editor_state(indoc! {"
 2985        /**
 2986
 2987        ˇ*/
 2988    "});
 2989        // Ensure that it detects suffix after existing prefix.
 2990        cx.set_state(indoc! {"
 2991        /**ˇ/
 2992    "});
 2993        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2994        cx.assert_editor_state(indoc! {"
 2995        /**
 2996        ˇ/
 2997    "});
 2998        // Ensure that if suffix exists on same line before
 2999        // cursor it does not add comment prefix.
 3000        cx.set_state(indoc! {"
 3001        /** */ˇ
 3002    "});
 3003        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3004        cx.assert_editor_state(indoc! {"
 3005        /** */
 3006        ˇ
 3007    "});
 3008        // Ensure that if suffix exists on same line before
 3009        // cursor it does not add comment prefix.
 3010        cx.set_state(indoc! {"
 3011        /**
 3012         *
 3013         */ˇ
 3014    "});
 3015        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3016        cx.assert_editor_state(indoc! {"
 3017        /**
 3018         *
 3019         */
 3020         ˇ
 3021    "});
 3022
 3023        // Ensure that inline comment followed by code
 3024        // doesn't add comment prefix on newline
 3025        cx.set_state(indoc! {"
 3026        /** */ textˇ
 3027    "});
 3028        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3029        cx.assert_editor_state(indoc! {"
 3030        /** */ text
 3031        ˇ
 3032    "});
 3033
 3034        // Ensure that text after comment end tag
 3035        // doesn't add comment prefix on newline
 3036        cx.set_state(indoc! {"
 3037        /**
 3038         *
 3039         */ˇtext
 3040    "});
 3041        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3042        cx.assert_editor_state(indoc! {"
 3043        /**
 3044         *
 3045         */
 3046         ˇtext
 3047    "});
 3048
 3049        // Ensure if not comment block it doesn't
 3050        // add comment prefix on newline
 3051        cx.set_state(indoc! {"
 3052        * textˇ
 3053    "});
 3054        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3055        cx.assert_editor_state(indoc! {"
 3056        * text
 3057        ˇ
 3058    "});
 3059    }
 3060    // Ensure that comment continuations can be disabled.
 3061    update_test_language_settings(cx, |settings| {
 3062        settings.defaults.extend_comment_on_newline = Some(false);
 3063    });
 3064    let mut cx = EditorTestContext::new(cx).await;
 3065    cx.set_state(indoc! {"
 3066        /**ˇ
 3067    "});
 3068    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3069    cx.assert_editor_state(indoc! {"
 3070        /**
 3071        ˇ
 3072    "});
 3073}
 3074
 3075#[gpui::test]
 3076fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3077    init_test(cx, |_| {});
 3078
 3079    let editor = cx.add_window(|window, cx| {
 3080        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3081        let mut editor = build_editor(buffer.clone(), window, cx);
 3082        editor.change_selections(None, window, cx, |s| {
 3083            s.select_ranges([3..4, 11..12, 19..20])
 3084        });
 3085        editor
 3086    });
 3087
 3088    _ = editor.update(cx, |editor, window, cx| {
 3089        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3090        editor.buffer.update(cx, |buffer, cx| {
 3091            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3092            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3093        });
 3094        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3095
 3096        editor.insert("Z", window, cx);
 3097        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3098
 3099        // The selections are moved after the inserted characters
 3100        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3101    });
 3102}
 3103
 3104#[gpui::test]
 3105async fn test_tab(cx: &mut TestAppContext) {
 3106    init_test(cx, |settings| {
 3107        settings.defaults.tab_size = NonZeroU32::new(3)
 3108    });
 3109
 3110    let mut cx = EditorTestContext::new(cx).await;
 3111    cx.set_state(indoc! {"
 3112        ˇabˇc
 3113        ˇ🏀ˇ🏀ˇefg
 3114 3115    "});
 3116    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3117    cx.assert_editor_state(indoc! {"
 3118           ˇab ˇc
 3119           ˇ🏀  ˇ🏀  ˇefg
 3120        d  ˇ
 3121    "});
 3122
 3123    cx.set_state(indoc! {"
 3124        a
 3125        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3126    "});
 3127    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3128    cx.assert_editor_state(indoc! {"
 3129        a
 3130           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3131    "});
 3132}
 3133
 3134#[gpui::test]
 3135async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3136    init_test(cx, |_| {});
 3137
 3138    let mut cx = EditorTestContext::new(cx).await;
 3139    let language = Arc::new(
 3140        Language::new(
 3141            LanguageConfig::default(),
 3142            Some(tree_sitter_rust::LANGUAGE.into()),
 3143        )
 3144        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3145        .unwrap(),
 3146    );
 3147    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3148
 3149    // test when all cursors are not at suggested indent
 3150    // then simply move to their suggested indent location
 3151    cx.set_state(indoc! {"
 3152        const a: B = (
 3153            c(
 3154        ˇ
 3155        ˇ    )
 3156        );
 3157    "});
 3158    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3159    cx.assert_editor_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162                ˇ
 3163            ˇ)
 3164        );
 3165    "});
 3166
 3167    // test cursor already at suggested indent not moving when
 3168    // other cursors are yet to reach their suggested indents
 3169    cx.set_state(indoc! {"
 3170        ˇ
 3171        const a: B = (
 3172            c(
 3173                d(
 3174        ˇ
 3175                )
 3176        ˇ
 3177        ˇ    )
 3178        );
 3179    "});
 3180    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3181    cx.assert_editor_state(indoc! {"
 3182        ˇ
 3183        const a: B = (
 3184            c(
 3185                d(
 3186                    ˇ
 3187                )
 3188                ˇ
 3189            ˇ)
 3190        );
 3191    "});
 3192    // test when all cursors are at suggested indent then tab is inserted
 3193    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3194    cx.assert_editor_state(indoc! {"
 3195            ˇ
 3196        const a: B = (
 3197            c(
 3198                d(
 3199                        ˇ
 3200                )
 3201                    ˇ
 3202                ˇ)
 3203        );
 3204    "});
 3205
 3206    // test when current indent is less than suggested indent,
 3207    // we adjust line to match suggested indent and move cursor to it
 3208    //
 3209    // when no other cursor is at word boundary, all of them should move
 3210    cx.set_state(indoc! {"
 3211        const a: B = (
 3212            c(
 3213                d(
 3214        ˇ
 3215        ˇ   )
 3216        ˇ   )
 3217        );
 3218    "});
 3219    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3220    cx.assert_editor_state(indoc! {"
 3221        const a: B = (
 3222            c(
 3223                d(
 3224                    ˇ
 3225                ˇ)
 3226            ˇ)
 3227        );
 3228    "});
 3229
 3230    // test when current indent is less than suggested indent,
 3231    // we adjust line to match suggested indent and move cursor to it
 3232    //
 3233    // when some other cursor is at word boundary, it should not move
 3234    cx.set_state(indoc! {"
 3235        const a: B = (
 3236            c(
 3237                d(
 3238        ˇ
 3239        ˇ   )
 3240           ˇ)
 3241        );
 3242    "});
 3243    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3244    cx.assert_editor_state(indoc! {"
 3245        const a: B = (
 3246            c(
 3247                d(
 3248                    ˇ
 3249                ˇ)
 3250            ˇ)
 3251        );
 3252    "});
 3253
 3254    // test when current indent is more than suggested indent,
 3255    // we just move cursor to current indent instead of suggested indent
 3256    //
 3257    // when no other cursor is at word boundary, all of them should move
 3258    cx.set_state(indoc! {"
 3259        const a: B = (
 3260            c(
 3261                d(
 3262        ˇ
 3263        ˇ                )
 3264        ˇ   )
 3265        );
 3266    "});
 3267    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3268    cx.assert_editor_state(indoc! {"
 3269        const a: B = (
 3270            c(
 3271                d(
 3272                    ˇ
 3273                        ˇ)
 3274            ˇ)
 3275        );
 3276    "});
 3277    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3278    cx.assert_editor_state(indoc! {"
 3279        const a: B = (
 3280            c(
 3281                d(
 3282                        ˇ
 3283                            ˇ)
 3284                ˇ)
 3285        );
 3286    "});
 3287
 3288    // test when current indent is more than suggested indent,
 3289    // we just move cursor to current indent instead of suggested indent
 3290    //
 3291    // when some other cursor is at word boundary, it doesn't move
 3292    cx.set_state(indoc! {"
 3293        const a: B = (
 3294            c(
 3295                d(
 3296        ˇ
 3297        ˇ                )
 3298            ˇ)
 3299        );
 3300    "});
 3301    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3302    cx.assert_editor_state(indoc! {"
 3303        const a: B = (
 3304            c(
 3305                d(
 3306                    ˇ
 3307                        ˇ)
 3308            ˇ)
 3309        );
 3310    "});
 3311
 3312    // handle auto-indent when there are multiple cursors on the same line
 3313    cx.set_state(indoc! {"
 3314        const a: B = (
 3315            c(
 3316        ˇ    ˇ
 3317        ˇ    )
 3318        );
 3319    "});
 3320    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3321    cx.assert_editor_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324                ˇ
 3325            ˇ)
 3326        );
 3327    "});
 3328}
 3329
 3330#[gpui::test]
 3331async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3332    init_test(cx, |settings| {
 3333        settings.defaults.tab_size = NonZeroU32::new(3)
 3334    });
 3335
 3336    let mut cx = EditorTestContext::new(cx).await;
 3337    cx.set_state(indoc! {"
 3338         ˇ
 3339        \t ˇ
 3340        \t  ˇ
 3341        \t   ˇ
 3342         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3343    "});
 3344
 3345    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3346    cx.assert_editor_state(indoc! {"
 3347           ˇ
 3348        \t   ˇ
 3349        \t   ˇ
 3350        \t      ˇ
 3351         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3352    "});
 3353}
 3354
 3355#[gpui::test]
 3356async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3357    init_test(cx, |settings| {
 3358        settings.defaults.tab_size = NonZeroU32::new(4)
 3359    });
 3360
 3361    let language = Arc::new(
 3362        Language::new(
 3363            LanguageConfig::default(),
 3364            Some(tree_sitter_rust::LANGUAGE.into()),
 3365        )
 3366        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3367        .unwrap(),
 3368    );
 3369
 3370    let mut cx = EditorTestContext::new(cx).await;
 3371    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3372    cx.set_state(indoc! {"
 3373        fn a() {
 3374            if b {
 3375        \t ˇc
 3376            }
 3377        }
 3378    "});
 3379
 3380    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3381    cx.assert_editor_state(indoc! {"
 3382        fn a() {
 3383            if b {
 3384                ˇc
 3385            }
 3386        }
 3387    "});
 3388}
 3389
 3390#[gpui::test]
 3391async fn test_indent_outdent(cx: &mut TestAppContext) {
 3392    init_test(cx, |settings| {
 3393        settings.defaults.tab_size = NonZeroU32::new(4);
 3394    });
 3395
 3396    let mut cx = EditorTestContext::new(cx).await;
 3397
 3398    cx.set_state(indoc! {"
 3399          «oneˇ» «twoˇ»
 3400        three
 3401         four
 3402    "});
 3403    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3404    cx.assert_editor_state(indoc! {"
 3405            «oneˇ» «twoˇ»
 3406        three
 3407         four
 3408    "});
 3409
 3410    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3411    cx.assert_editor_state(indoc! {"
 3412        «oneˇ» «twoˇ»
 3413        three
 3414         four
 3415    "});
 3416
 3417    // select across line ending
 3418    cx.set_state(indoc! {"
 3419        one two
 3420        t«hree
 3421        ˇ» four
 3422    "});
 3423    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3424    cx.assert_editor_state(indoc! {"
 3425        one two
 3426            t«hree
 3427        ˇ» four
 3428    "});
 3429
 3430    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3431    cx.assert_editor_state(indoc! {"
 3432        one two
 3433        t«hree
 3434        ˇ» four
 3435    "});
 3436
 3437    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3438    cx.set_state(indoc! {"
 3439        one two
 3440        ˇthree
 3441            four
 3442    "});
 3443    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3444    cx.assert_editor_state(indoc! {"
 3445        one two
 3446            ˇthree
 3447            four
 3448    "});
 3449
 3450    cx.set_state(indoc! {"
 3451        one two
 3452        ˇ    three
 3453            four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457        one two
 3458        ˇthree
 3459            four
 3460    "});
 3461}
 3462
 3463#[gpui::test]
 3464async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3465    init_test(cx, |settings| {
 3466        settings.defaults.hard_tabs = Some(true);
 3467    });
 3468
 3469    let mut cx = EditorTestContext::new(cx).await;
 3470
 3471    // select two ranges on one line
 3472    cx.set_state(indoc! {"
 3473        «oneˇ» «twoˇ»
 3474        three
 3475        four
 3476    "});
 3477    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        \t«oneˇ» «twoˇ»
 3480        three
 3481        four
 3482    "});
 3483    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3484    cx.assert_editor_state(indoc! {"
 3485        \t\t«oneˇ» «twoˇ»
 3486        three
 3487        four
 3488    "});
 3489    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3490    cx.assert_editor_state(indoc! {"
 3491        \t«oneˇ» «twoˇ»
 3492        three
 3493        four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        «oneˇ» «twoˇ»
 3498        three
 3499        four
 3500    "});
 3501
 3502    // select across a line ending
 3503    cx.set_state(indoc! {"
 3504        one two
 3505        t«hree
 3506        ˇ»four
 3507    "});
 3508    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3509    cx.assert_editor_state(indoc! {"
 3510        one two
 3511        \tt«hree
 3512        ˇ»four
 3513    "});
 3514    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3515    cx.assert_editor_state(indoc! {"
 3516        one two
 3517        \t\tt«hree
 3518        ˇ»four
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        one two
 3523        \tt«hree
 3524        ˇ»four
 3525    "});
 3526    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3527    cx.assert_editor_state(indoc! {"
 3528        one two
 3529        t«hree
 3530        ˇ»four
 3531    "});
 3532
 3533    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3534    cx.set_state(indoc! {"
 3535        one two
 3536        ˇthree
 3537        four
 3538    "});
 3539    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3540    cx.assert_editor_state(indoc! {"
 3541        one two
 3542        ˇthree
 3543        four
 3544    "});
 3545    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3546    cx.assert_editor_state(indoc! {"
 3547        one two
 3548        \tˇthree
 3549        four
 3550    "});
 3551    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3552    cx.assert_editor_state(indoc! {"
 3553        one two
 3554        ˇthree
 3555        four
 3556    "});
 3557}
 3558
 3559#[gpui::test]
 3560fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3561    init_test(cx, |settings| {
 3562        settings.languages.extend([
 3563            (
 3564                "TOML".into(),
 3565                LanguageSettingsContent {
 3566                    tab_size: NonZeroU32::new(2),
 3567                    ..Default::default()
 3568                },
 3569            ),
 3570            (
 3571                "Rust".into(),
 3572                LanguageSettingsContent {
 3573                    tab_size: NonZeroU32::new(4),
 3574                    ..Default::default()
 3575                },
 3576            ),
 3577        ]);
 3578    });
 3579
 3580    let toml_language = Arc::new(Language::new(
 3581        LanguageConfig {
 3582            name: "TOML".into(),
 3583            ..Default::default()
 3584        },
 3585        None,
 3586    ));
 3587    let rust_language = Arc::new(Language::new(
 3588        LanguageConfig {
 3589            name: "Rust".into(),
 3590            ..Default::default()
 3591        },
 3592        None,
 3593    ));
 3594
 3595    let toml_buffer =
 3596        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3597    let rust_buffer =
 3598        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3599    let multibuffer = cx.new(|cx| {
 3600        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3601        multibuffer.push_excerpts(
 3602            toml_buffer.clone(),
 3603            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3604            cx,
 3605        );
 3606        multibuffer.push_excerpts(
 3607            rust_buffer.clone(),
 3608            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3609            cx,
 3610        );
 3611        multibuffer
 3612    });
 3613
 3614    cx.add_window(|window, cx| {
 3615        let mut editor = build_editor(multibuffer, window, cx);
 3616
 3617        assert_eq!(
 3618            editor.text(cx),
 3619            indoc! {"
 3620                a = 1
 3621                b = 2
 3622
 3623                const c: usize = 3;
 3624            "}
 3625        );
 3626
 3627        select_ranges(
 3628            &mut editor,
 3629            indoc! {"
 3630                «aˇ» = 1
 3631                b = 2
 3632
 3633                «const c:ˇ» usize = 3;
 3634            "},
 3635            window,
 3636            cx,
 3637        );
 3638
 3639        editor.tab(&Tab, window, cx);
 3640        assert_text_with_selections(
 3641            &mut editor,
 3642            indoc! {"
 3643                  «aˇ» = 1
 3644                b = 2
 3645
 3646                    «const c:ˇ» usize = 3;
 3647            "},
 3648            cx,
 3649        );
 3650        editor.backtab(&Backtab, window, cx);
 3651        assert_text_with_selections(
 3652            &mut editor,
 3653            indoc! {"
 3654                «aˇ» = 1
 3655                b = 2
 3656
 3657                «const c:ˇ» usize = 3;
 3658            "},
 3659            cx,
 3660        );
 3661
 3662        editor
 3663    });
 3664}
 3665
 3666#[gpui::test]
 3667async fn test_backspace(cx: &mut TestAppContext) {
 3668    init_test(cx, |_| {});
 3669
 3670    let mut cx = EditorTestContext::new(cx).await;
 3671
 3672    // Basic backspace
 3673    cx.set_state(indoc! {"
 3674        onˇe two three
 3675        fou«rˇ» five six
 3676        seven «ˇeight nine
 3677        »ten
 3678    "});
 3679    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3680    cx.assert_editor_state(indoc! {"
 3681        oˇe two three
 3682        fouˇ five six
 3683        seven ˇten
 3684    "});
 3685
 3686    // Test backspace inside and around indents
 3687    cx.set_state(indoc! {"
 3688        zero
 3689            ˇone
 3690                ˇtwo
 3691            ˇ ˇ ˇ  three
 3692        ˇ  ˇ  four
 3693    "});
 3694    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3695    cx.assert_editor_state(indoc! {"
 3696        zero
 3697        ˇone
 3698            ˇtwo
 3699        ˇ  threeˇ  four
 3700    "});
 3701}
 3702
 3703#[gpui::test]
 3704async fn test_delete(cx: &mut TestAppContext) {
 3705    init_test(cx, |_| {});
 3706
 3707    let mut cx = EditorTestContext::new(cx).await;
 3708    cx.set_state(indoc! {"
 3709        onˇe two three
 3710        fou«rˇ» five six
 3711        seven «ˇeight nine
 3712        »ten
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        onˇ two three
 3717        fouˇ five six
 3718        seven ˇten
 3719    "});
 3720}
 3721
 3722#[gpui::test]
 3723fn test_delete_line(cx: &mut TestAppContext) {
 3724    init_test(cx, |_| {});
 3725
 3726    let editor = cx.add_window(|window, cx| {
 3727        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3728        build_editor(buffer, window, cx)
 3729    });
 3730    _ = editor.update(cx, |editor, window, cx| {
 3731        editor.change_selections(None, window, cx, |s| {
 3732            s.select_display_ranges([
 3733                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3734                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3735                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3736            ])
 3737        });
 3738        editor.delete_line(&DeleteLine, window, cx);
 3739        assert_eq!(editor.display_text(cx), "ghi");
 3740        assert_eq!(
 3741            editor.selections.display_ranges(cx),
 3742            vec![
 3743                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3744                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3745            ]
 3746        );
 3747    });
 3748
 3749    let editor = cx.add_window(|window, cx| {
 3750        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3751        build_editor(buffer, window, cx)
 3752    });
 3753    _ = editor.update(cx, |editor, window, cx| {
 3754        editor.change_selections(None, window, cx, |s| {
 3755            s.select_display_ranges([
 3756                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3757            ])
 3758        });
 3759        editor.delete_line(&DeleteLine, window, cx);
 3760        assert_eq!(editor.display_text(cx), "ghi\n");
 3761        assert_eq!(
 3762            editor.selections.display_ranges(cx),
 3763            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3764        );
 3765    });
 3766}
 3767
 3768#[gpui::test]
 3769fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3770    init_test(cx, |_| {});
 3771
 3772    cx.add_window(|window, cx| {
 3773        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3774        let mut editor = build_editor(buffer.clone(), window, cx);
 3775        let buffer = buffer.read(cx).as_singleton().unwrap();
 3776
 3777        assert_eq!(
 3778            editor.selections.ranges::<Point>(cx),
 3779            &[Point::new(0, 0)..Point::new(0, 0)]
 3780        );
 3781
 3782        // When on single line, replace newline at end by space
 3783        editor.join_lines(&JoinLines, window, cx);
 3784        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 3)..Point::new(0, 3)]
 3788        );
 3789
 3790        // When multiple lines are selected, remove newlines that are spanned by the selection
 3791        editor.change_selections(None, window, cx, |s| {
 3792            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3793        });
 3794        editor.join_lines(&JoinLines, window, cx);
 3795        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3796        assert_eq!(
 3797            editor.selections.ranges::<Point>(cx),
 3798            &[Point::new(0, 11)..Point::new(0, 11)]
 3799        );
 3800
 3801        // Undo should be transactional
 3802        editor.undo(&Undo, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            &[Point::new(0, 5)..Point::new(2, 2)]
 3807        );
 3808
 3809        // When joining an empty line don't insert a space
 3810        editor.change_selections(None, window, cx, |s| {
 3811            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3812        });
 3813        editor.join_lines(&JoinLines, window, cx);
 3814        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3815        assert_eq!(
 3816            editor.selections.ranges::<Point>(cx),
 3817            [Point::new(2, 3)..Point::new(2, 3)]
 3818        );
 3819
 3820        // We can remove trailing newlines
 3821        editor.join_lines(&JoinLines, window, cx);
 3822        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3823        assert_eq!(
 3824            editor.selections.ranges::<Point>(cx),
 3825            [Point::new(2, 3)..Point::new(2, 3)]
 3826        );
 3827
 3828        // We don't blow up on the last line
 3829        editor.join_lines(&JoinLines, window, cx);
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3831        assert_eq!(
 3832            editor.selections.ranges::<Point>(cx),
 3833            [Point::new(2, 3)..Point::new(2, 3)]
 3834        );
 3835
 3836        // reset to test indentation
 3837        editor.buffer.update(cx, |buffer, cx| {
 3838            buffer.edit(
 3839                [
 3840                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3841                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3842                ],
 3843                None,
 3844                cx,
 3845            )
 3846        });
 3847
 3848        // We remove any leading spaces
 3849        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3850        editor.change_selections(None, window, cx, |s| {
 3851            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3852        });
 3853        editor.join_lines(&JoinLines, window, cx);
 3854        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3855
 3856        // We don't insert a space for a line containing only spaces
 3857        editor.join_lines(&JoinLines, window, cx);
 3858        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3859
 3860        // We ignore any leading tabs
 3861        editor.join_lines(&JoinLines, window, cx);
 3862        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3863
 3864        editor
 3865    });
 3866}
 3867
 3868#[gpui::test]
 3869fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3870    init_test(cx, |_| {});
 3871
 3872    cx.add_window(|window, cx| {
 3873        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3874        let mut editor = build_editor(buffer.clone(), window, cx);
 3875        let buffer = buffer.read(cx).as_singleton().unwrap();
 3876
 3877        editor.change_selections(None, window, cx, |s| {
 3878            s.select_ranges([
 3879                Point::new(0, 2)..Point::new(1, 1),
 3880                Point::new(1, 2)..Point::new(1, 2),
 3881                Point::new(3, 1)..Point::new(3, 2),
 3882            ])
 3883        });
 3884
 3885        editor.join_lines(&JoinLines, window, cx);
 3886        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3887
 3888        assert_eq!(
 3889            editor.selections.ranges::<Point>(cx),
 3890            [
 3891                Point::new(0, 7)..Point::new(0, 7),
 3892                Point::new(1, 3)..Point::new(1, 3)
 3893            ]
 3894        );
 3895        editor
 3896    });
 3897}
 3898
 3899#[gpui::test]
 3900async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3901    init_test(cx, |_| {});
 3902
 3903    let mut cx = EditorTestContext::new(cx).await;
 3904
 3905    let diff_base = r#"
 3906        Line 0
 3907        Line 1
 3908        Line 2
 3909        Line 3
 3910        "#
 3911    .unindent();
 3912
 3913    cx.set_state(
 3914        &r#"
 3915        ˇLine 0
 3916        Line 1
 3917        Line 2
 3918        Line 3
 3919        "#
 3920        .unindent(),
 3921    );
 3922
 3923    cx.set_head_text(&diff_base);
 3924    executor.run_until_parked();
 3925
 3926    // Join lines
 3927    cx.update_editor(|editor, window, cx| {
 3928        editor.join_lines(&JoinLines, window, cx);
 3929    });
 3930    executor.run_until_parked();
 3931
 3932    cx.assert_editor_state(
 3933        &r#"
 3934        Line 0ˇ Line 1
 3935        Line 2
 3936        Line 3
 3937        "#
 3938        .unindent(),
 3939    );
 3940    // Join again
 3941    cx.update_editor(|editor, window, cx| {
 3942        editor.join_lines(&JoinLines, window, cx);
 3943    });
 3944    executor.run_until_parked();
 3945
 3946    cx.assert_editor_state(
 3947        &r#"
 3948        Line 0 Line 1ˇ Line 2
 3949        Line 3
 3950        "#
 3951        .unindent(),
 3952    );
 3953}
 3954
 3955#[gpui::test]
 3956async fn test_custom_newlines_cause_no_false_positive_diffs(
 3957    executor: BackgroundExecutor,
 3958    cx: &mut TestAppContext,
 3959) {
 3960    init_test(cx, |_| {});
 3961    let mut cx = EditorTestContext::new(cx).await;
 3962    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3963    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3964    executor.run_until_parked();
 3965
 3966    cx.update_editor(|editor, window, cx| {
 3967        let snapshot = editor.snapshot(window, cx);
 3968        assert_eq!(
 3969            snapshot
 3970                .buffer_snapshot
 3971                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3972                .collect::<Vec<_>>(),
 3973            Vec::new(),
 3974            "Should not have any diffs for files with custom newlines"
 3975        );
 3976    });
 3977}
 3978
 3979#[gpui::test]
 3980async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 3981    init_test(cx, |_| {});
 3982
 3983    let mut cx = EditorTestContext::new(cx).await;
 3984
 3985    // Test sort_lines_case_insensitive()
 3986    cx.set_state(indoc! {"
 3987        «z
 3988        y
 3989        x
 3990        Z
 3991        Y
 3992        Xˇ»
 3993    "});
 3994    cx.update_editor(|e, window, cx| {
 3995        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3996    });
 3997    cx.assert_editor_state(indoc! {"
 3998        «x
 3999        X
 4000        y
 4001        Y
 4002        z
 4003        Zˇ»
 4004    "});
 4005
 4006    // Test reverse_lines()
 4007    cx.set_state(indoc! {"
 4008        «5
 4009        4
 4010        3
 4011        2
 4012        1ˇ»
 4013    "});
 4014    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4015    cx.assert_editor_state(indoc! {"
 4016        «1
 4017        2
 4018        3
 4019        4
 4020        5ˇ»
 4021    "});
 4022
 4023    // Skip testing shuffle_line()
 4024
 4025    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4026    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4027
 4028    // Don't manipulate when cursor is on single line, but expand the selection
 4029    cx.set_state(indoc! {"
 4030        ddˇdd
 4031        ccc
 4032        bb
 4033        a
 4034    "});
 4035    cx.update_editor(|e, window, cx| {
 4036        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4037    });
 4038    cx.assert_editor_state(indoc! {"
 4039        «ddddˇ»
 4040        ccc
 4041        bb
 4042        a
 4043    "});
 4044
 4045    // Basic manipulate case
 4046    // Start selection moves to column 0
 4047    // End of selection shrinks to fit shorter line
 4048    cx.set_state(indoc! {"
 4049        dd«d
 4050        ccc
 4051        bb
 4052        aaaaaˇ»
 4053    "});
 4054    cx.update_editor(|e, window, cx| {
 4055        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4056    });
 4057    cx.assert_editor_state(indoc! {"
 4058        «aaaaa
 4059        bb
 4060        ccc
 4061        dddˇ»
 4062    "});
 4063
 4064    // Manipulate case with newlines
 4065    cx.set_state(indoc! {"
 4066        dd«d
 4067        ccc
 4068
 4069        bb
 4070        aaaaa
 4071
 4072        ˇ»
 4073    "});
 4074    cx.update_editor(|e, window, cx| {
 4075        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4076    });
 4077    cx.assert_editor_state(indoc! {"
 4078        «
 4079
 4080        aaaaa
 4081        bb
 4082        ccc
 4083        dddˇ»
 4084
 4085    "});
 4086
 4087    // Adding new line
 4088    cx.set_state(indoc! {"
 4089        aa«a
 4090        bbˇ»b
 4091    "});
 4092    cx.update_editor(|e, window, cx| {
 4093        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4094    });
 4095    cx.assert_editor_state(indoc! {"
 4096        «aaa
 4097        bbb
 4098        added_lineˇ»
 4099    "});
 4100
 4101    // Removing line
 4102    cx.set_state(indoc! {"
 4103        aa«a
 4104        bbbˇ»
 4105    "});
 4106    cx.update_editor(|e, window, cx| {
 4107        e.manipulate_immutable_lines(window, cx, |lines| {
 4108            lines.pop();
 4109        })
 4110    });
 4111    cx.assert_editor_state(indoc! {"
 4112        «aaaˇ»
 4113    "});
 4114
 4115    // Removing all lines
 4116    cx.set_state(indoc! {"
 4117        aa«a
 4118        bbbˇ»
 4119    "});
 4120    cx.update_editor(|e, window, cx| {
 4121        e.manipulate_immutable_lines(window, cx, |lines| {
 4122            lines.drain(..);
 4123        })
 4124    });
 4125    cx.assert_editor_state(indoc! {"
 4126        ˇ
 4127    "});
 4128}
 4129
 4130#[gpui::test]
 4131async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4132    init_test(cx, |_| {});
 4133
 4134    let mut cx = EditorTestContext::new(cx).await;
 4135
 4136    // Consider continuous selection as single selection
 4137    cx.set_state(indoc! {"
 4138        Aaa«aa
 4139        cˇ»c«c
 4140        bb
 4141        aaaˇ»aa
 4142    "});
 4143    cx.update_editor(|e, window, cx| {
 4144        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4145    });
 4146    cx.assert_editor_state(indoc! {"
 4147        «Aaaaa
 4148        ccc
 4149        bb
 4150        aaaaaˇ»
 4151    "});
 4152
 4153    cx.set_state(indoc! {"
 4154        Aaa«aa
 4155        cˇ»c«c
 4156        bb
 4157        aaaˇ»aa
 4158    "});
 4159    cx.update_editor(|e, window, cx| {
 4160        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4161    });
 4162    cx.assert_editor_state(indoc! {"
 4163        «Aaaaa
 4164        ccc
 4165        bbˇ»
 4166    "});
 4167
 4168    // Consider non continuous selection as distinct dedup operations
 4169    cx.set_state(indoc! {"
 4170        «aaaaa
 4171        bb
 4172        aaaaa
 4173        aaaaaˇ»
 4174
 4175        aaa«aaˇ»
 4176    "});
 4177    cx.update_editor(|e, window, cx| {
 4178        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4179    });
 4180    cx.assert_editor_state(indoc! {"
 4181        «aaaaa
 4182        bbˇ»
 4183
 4184        «aaaaaˇ»
 4185    "});
 4186}
 4187
 4188#[gpui::test]
 4189async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4190    init_test(cx, |_| {});
 4191
 4192    let mut cx = EditorTestContext::new(cx).await;
 4193
 4194    cx.set_state(indoc! {"
 4195        «Aaa
 4196        aAa
 4197        Aaaˇ»
 4198    "});
 4199    cx.update_editor(|e, window, cx| {
 4200        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4201    });
 4202    cx.assert_editor_state(indoc! {"
 4203        «Aaa
 4204        aAaˇ»
 4205    "});
 4206
 4207    cx.set_state(indoc! {"
 4208        «Aaa
 4209        aAa
 4210        aaAˇ»
 4211    "});
 4212    cx.update_editor(|e, window, cx| {
 4213        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4214    });
 4215    cx.assert_editor_state(indoc! {"
 4216        «Aaaˇ»
 4217    "});
 4218}
 4219
 4220#[gpui::test]
 4221async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4222    init_test(cx, |_| {});
 4223
 4224    let mut cx = EditorTestContext::new(cx).await;
 4225
 4226    // Manipulate with multiple selections on a single line
 4227    cx.set_state(indoc! {"
 4228        dd«dd
 4229        cˇ»c«c
 4230        bb
 4231        aaaˇ»aa
 4232    "});
 4233    cx.update_editor(|e, window, cx| {
 4234        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4235    });
 4236    cx.assert_editor_state(indoc! {"
 4237        «aaaaa
 4238        bb
 4239        ccc
 4240        ddddˇ»
 4241    "});
 4242
 4243    // Manipulate with multiple disjoin selections
 4244    cx.set_state(indoc! {"
 4245 4246        4
 4247        3
 4248        2
 4249        1ˇ»
 4250
 4251        dd«dd
 4252        ccc
 4253        bb
 4254        aaaˇ»aa
 4255    "});
 4256    cx.update_editor(|e, window, cx| {
 4257        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4258    });
 4259    cx.assert_editor_state(indoc! {"
 4260        «1
 4261        2
 4262        3
 4263        4
 4264        5ˇ»
 4265
 4266        «aaaaa
 4267        bb
 4268        ccc
 4269        ddddˇ»
 4270    "});
 4271
 4272    // Adding lines on each selection
 4273    cx.set_state(indoc! {"
 4274 4275        1ˇ»
 4276
 4277        bb«bb
 4278        aaaˇ»aa
 4279    "});
 4280    cx.update_editor(|e, window, cx| {
 4281        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4282    });
 4283    cx.assert_editor_state(indoc! {"
 4284        «2
 4285        1
 4286        added lineˇ»
 4287
 4288        «bbbb
 4289        aaaaa
 4290        added lineˇ»
 4291    "});
 4292
 4293    // Removing lines on each selection
 4294    cx.set_state(indoc! {"
 4295 4296        1ˇ»
 4297
 4298        bb«bb
 4299        aaaˇ»aa
 4300    "});
 4301    cx.update_editor(|e, window, cx| {
 4302        e.manipulate_immutable_lines(window, cx, |lines| {
 4303            lines.pop();
 4304        })
 4305    });
 4306    cx.assert_editor_state(indoc! {"
 4307        «2ˇ»
 4308
 4309        «bbbbˇ»
 4310    "});
 4311}
 4312
 4313#[gpui::test]
 4314async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4315    init_test(cx, |settings| {
 4316        settings.defaults.tab_size = NonZeroU32::new(3)
 4317    });
 4318
 4319    let mut cx = EditorTestContext::new(cx).await;
 4320
 4321    // MULTI SELECTION
 4322    // Ln.1 "«" tests empty lines
 4323    // Ln.9 tests just leading whitespace
 4324    cx.set_state(indoc! {"
 4325        «
 4326        abc                 // No indentationˇ»
 4327        «\tabc              // 1 tabˇ»
 4328        \t\tabc «      ˇ»   // 2 tabs
 4329        \t ab«c             // Tab followed by space
 4330         \tabc              // Space followed by tab (3 spaces should be the result)
 4331        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4332           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4333        \t
 4334        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4335    "});
 4336    cx.update_editor(|e, window, cx| {
 4337        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4338    });
 4339    cx.assert_editor_state(indoc! {"
 4340        «
 4341        abc                 // No indentation
 4342           abc              // 1 tab
 4343              abc          // 2 tabs
 4344            abc             // Tab followed by space
 4345           abc              // Space followed by tab (3 spaces should be the result)
 4346                       abc   // Mixed indentation (tab conversion depends on the column)
 4347           abc         // Already space indented
 4348           
 4349           abc\tdef          // Only the leading tab is manipulatedˇ»
 4350    "});
 4351
 4352    // Test on just a few lines, the others should remain unchanged
 4353    // Only lines (3, 5, 10, 11) should change
 4354    cx.set_state(indoc! {"
 4355        
 4356        abc                 // No indentation
 4357        \tabcˇ               // 1 tab
 4358        \t\tabc             // 2 tabs
 4359        \t abcˇ              // Tab followed by space
 4360         \tabc              // Space followed by tab (3 spaces should be the result)
 4361        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4362           abc              // Already space indented
 4363        «\t
 4364        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4365    "});
 4366    cx.update_editor(|e, window, cx| {
 4367        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4368    });
 4369    cx.assert_editor_state(indoc! {"
 4370        
 4371        abc                 // No indentation
 4372        «   abc               // 1 tabˇ»
 4373        \t\tabc             // 2 tabs
 4374        «    abc              // Tab followed by spaceˇ»
 4375         \tabc              // Space followed by tab (3 spaces should be the result)
 4376        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4377           abc              // Already space indented
 4378        «   
 4379           abc\tdef          // Only the leading tab is manipulatedˇ»
 4380    "});
 4381
 4382    // SINGLE SELECTION
 4383    // Ln.1 "«" tests empty lines
 4384    // Ln.9 tests just leading whitespace
 4385    cx.set_state(indoc! {"
 4386        «
 4387        abc                 // No indentation
 4388        \tabc               // 1 tab
 4389        \t\tabc             // 2 tabs
 4390        \t abc              // Tab followed by space
 4391         \tabc              // Space followed by tab (3 spaces should be the result)
 4392        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4393           abc              // Already space indented
 4394        \t
 4395        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4396    "});
 4397    cx.update_editor(|e, window, cx| {
 4398        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4399    });
 4400    cx.assert_editor_state(indoc! {"
 4401        «
 4402        abc                 // No indentation
 4403           abc               // 1 tab
 4404              abc             // 2 tabs
 4405            abc              // Tab followed by space
 4406           abc              // Space followed by tab (3 spaces should be the result)
 4407                       abc   // Mixed indentation (tab conversion depends on the column)
 4408           abc              // Already space indented
 4409           
 4410           abc\tdef          // Only the leading tab is manipulatedˇ»
 4411    "});
 4412}
 4413
 4414#[gpui::test]
 4415async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4416    init_test(cx, |settings| {
 4417        settings.defaults.tab_size = NonZeroU32::new(3)
 4418    });
 4419
 4420    let mut cx = EditorTestContext::new(cx).await;
 4421
 4422    // MULTI SELECTION
 4423    // Ln.1 "«" tests empty lines
 4424    // Ln.11 tests just leading whitespace
 4425    cx.set_state(indoc! {"
 4426        «
 4427        abˇ»ˇc                 // No indentation
 4428         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4429          abc  «             // 2 spaces (< 3 so dont convert)
 4430           abc              // 3 spaces (convert)
 4431             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4432        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4433        «\t abc              // Tab followed by space
 4434         \tabc              // Space followed by tab (should be consumed due to tab)
 4435        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4436           \tˇ»  «\t
 4437           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4438    "});
 4439    cx.update_editor(|e, window, cx| {
 4440        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4441    });
 4442    cx.assert_editor_state(indoc! {"
 4443        «
 4444        abc                 // No indentation
 4445         abc                // 1 space (< 3 so dont convert)
 4446          abc               // 2 spaces (< 3 so dont convert)
 4447        \tabc              // 3 spaces (convert)
 4448        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4449        \t\t\tabc           // Already tab indented
 4450        \t abc              // Tab followed by space
 4451        \tabc              // Space followed by tab (should be consumed due to tab)
 4452        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4453        \t\t\t
 4454        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4455    "});
 4456
 4457    // Test on just a few lines, the other should remain unchanged
 4458    // Only lines (4, 8, 11, 12) should change
 4459    cx.set_state(indoc! {"
 4460        
 4461        abc                 // No indentation
 4462         abc                // 1 space (< 3 so dont convert)
 4463          abc               // 2 spaces (< 3 so dont convert)
 4464        «   abc              // 3 spaces (convert)ˇ»
 4465             abc            // 5 spaces (1 tab + 2 spaces)
 4466        \t\t\tabc           // Already tab indented
 4467        \t abc              // Tab followed by space
 4468         \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4469           \t\t  \tabc      // Mixed indentation
 4470        \t \t  \t   \tabc   // Mixed indentation
 4471           \t  \tˇ
 4472        «   abc   \t         // Only the leading spaces should be convertedˇ»
 4473    "});
 4474    cx.update_editor(|e, window, cx| {
 4475        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4476    });
 4477    cx.assert_editor_state(indoc! {"
 4478        
 4479        abc                 // No indentation
 4480         abc                // 1 space (< 3 so dont convert)
 4481          abc               // 2 spaces (< 3 so dont convert)
 4482        «\tabc              // 3 spaces (convert)ˇ»
 4483             abc            // 5 spaces (1 tab + 2 spaces)
 4484        \t\t\tabc           // Already tab indented
 4485        \t abc              // Tab followed by space
 4486        «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4487           \t\t  \tabc      // Mixed indentation
 4488        \t \t  \t   \tabc   // Mixed indentation
 4489        «\t\t\t
 4490        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4491    "});
 4492
 4493    // SINGLE SELECTION
 4494    // Ln.1 "«" tests empty lines
 4495    // Ln.11 tests just leading whitespace
 4496    cx.set_state(indoc! {"
 4497        «
 4498        abc                 // No indentation
 4499         abc                // 1 space (< 3 so dont convert)
 4500          abc               // 2 spaces (< 3 so dont convert)
 4501           abc              // 3 spaces (convert)
 4502             abc            // 5 spaces (1 tab + 2 spaces)
 4503        \t\t\tabc           // Already tab indented
 4504        \t abc              // Tab followed by space
 4505         \tabc              // Space followed by tab (should be consumed due to tab)
 4506        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4507           \t  \t
 4508           abc   \t         // Only the leading spaces should be convertedˇ»
 4509    "});
 4510    cx.update_editor(|e, window, cx| {
 4511        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4512    });
 4513    cx.assert_editor_state(indoc! {"
 4514        «
 4515        abc                 // No indentation
 4516         abc                // 1 space (< 3 so dont convert)
 4517          abc               // 2 spaces (< 3 so dont convert)
 4518        \tabc              // 3 spaces (convert)
 4519        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4520        \t\t\tabc           // Already tab indented
 4521        \t abc              // Tab followed by space
 4522        \tabc              // Space followed by tab (should be consumed due to tab)
 4523        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4524        \t\t\t
 4525        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4526    "});
 4527}
 4528
 4529#[gpui::test]
 4530async fn test_toggle_case(cx: &mut TestAppContext) {
 4531    init_test(cx, |_| {});
 4532
 4533    let mut cx = EditorTestContext::new(cx).await;
 4534
 4535    // If all lower case -> upper case
 4536    cx.set_state(indoc! {"
 4537        «hello worldˇ»
 4538    "});
 4539    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4540    cx.assert_editor_state(indoc! {"
 4541        «HELLO WORLDˇ»
 4542    "});
 4543
 4544    // If all upper case -> lower case
 4545    cx.set_state(indoc! {"
 4546        «HELLO WORLDˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4549    cx.assert_editor_state(indoc! {"
 4550        «hello worldˇ»
 4551    "});
 4552
 4553    // If any upper case characters are identified -> lower case
 4554    // This matches JetBrains IDEs
 4555    cx.set_state(indoc! {"
 4556        «hEllo worldˇ»
 4557    "});
 4558    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4559    cx.assert_editor_state(indoc! {"
 4560        «hello worldˇ»
 4561    "});
 4562}
 4563
 4564#[gpui::test]
 4565async fn test_manipulate_text(cx: &mut TestAppContext) {
 4566    init_test(cx, |_| {});
 4567
 4568    let mut cx = EditorTestContext::new(cx).await;
 4569
 4570    // Test convert_to_upper_case()
 4571    cx.set_state(indoc! {"
 4572        «hello worldˇ»
 4573    "});
 4574    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4575    cx.assert_editor_state(indoc! {"
 4576        «HELLO WORLDˇ»
 4577    "});
 4578
 4579    // Test convert_to_lower_case()
 4580    cx.set_state(indoc! {"
 4581        «HELLO WORLDˇ»
 4582    "});
 4583    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4584    cx.assert_editor_state(indoc! {"
 4585        «hello worldˇ»
 4586    "});
 4587
 4588    // Test multiple line, single selection case
 4589    cx.set_state(indoc! {"
 4590        «The quick brown
 4591        fox jumps over
 4592        the lazy dogˇ»
 4593    "});
 4594    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4595    cx.assert_editor_state(indoc! {"
 4596        «The Quick Brown
 4597        Fox Jumps Over
 4598        The Lazy Dogˇ»
 4599    "});
 4600
 4601    // Test multiple line, single selection case
 4602    cx.set_state(indoc! {"
 4603        «The quick brown
 4604        fox jumps over
 4605        the lazy dogˇ»
 4606    "});
 4607    cx.update_editor(|e, window, cx| {
 4608        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4609    });
 4610    cx.assert_editor_state(indoc! {"
 4611        «TheQuickBrown
 4612        FoxJumpsOver
 4613        TheLazyDogˇ»
 4614    "});
 4615
 4616    // From here on out, test more complex cases of manipulate_text()
 4617
 4618    // Test no selection case - should affect words cursors are in
 4619    // Cursor at beginning, middle, and end of word
 4620    cx.set_state(indoc! {"
 4621        ˇhello big beauˇtiful worldˇ
 4622    "});
 4623    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4624    cx.assert_editor_state(indoc! {"
 4625        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4626    "});
 4627
 4628    // Test multiple selections on a single line and across multiple lines
 4629    cx.set_state(indoc! {"
 4630        «Theˇ» quick «brown
 4631        foxˇ» jumps «overˇ»
 4632        the «lazyˇ» dog
 4633    "});
 4634    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4635    cx.assert_editor_state(indoc! {"
 4636        «THEˇ» quick «BROWN
 4637        FOXˇ» jumps «OVERˇ»
 4638        the «LAZYˇ» dog
 4639    "});
 4640
 4641    // Test case where text length grows
 4642    cx.set_state(indoc! {"
 4643        «tschüߡ»
 4644    "});
 4645    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4646    cx.assert_editor_state(indoc! {"
 4647        «TSCHÜSSˇ»
 4648    "});
 4649
 4650    // Test to make sure we don't crash when text shrinks
 4651    cx.set_state(indoc! {"
 4652        aaa_bbbˇ
 4653    "});
 4654    cx.update_editor(|e, window, cx| {
 4655        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4656    });
 4657    cx.assert_editor_state(indoc! {"
 4658        «aaaBbbˇ»
 4659    "});
 4660
 4661    // Test to make sure we all aware of the fact that each word can grow and shrink
 4662    // Final selections should be aware of this fact
 4663    cx.set_state(indoc! {"
 4664        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4665    "});
 4666    cx.update_editor(|e, window, cx| {
 4667        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4668    });
 4669    cx.assert_editor_state(indoc! {"
 4670        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4671    "});
 4672
 4673    cx.set_state(indoc! {"
 4674        «hElLo, WoRld!ˇ»
 4675    "});
 4676    cx.update_editor(|e, window, cx| {
 4677        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4678    });
 4679    cx.assert_editor_state(indoc! {"
 4680        «HeLlO, wOrLD!ˇ»
 4681    "});
 4682}
 4683
 4684#[gpui::test]
 4685fn test_duplicate_line(cx: &mut TestAppContext) {
 4686    init_test(cx, |_| {});
 4687
 4688    let editor = cx.add_window(|window, cx| {
 4689        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4690        build_editor(buffer, window, cx)
 4691    });
 4692    _ = editor.update(cx, |editor, window, cx| {
 4693        editor.change_selections(None, window, cx, |s| {
 4694            s.select_display_ranges([
 4695                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4696                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4697                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4698                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4699            ])
 4700        });
 4701        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4702        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4703        assert_eq!(
 4704            editor.selections.display_ranges(cx),
 4705            vec![
 4706                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4708                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4709                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4710            ]
 4711        );
 4712    });
 4713
 4714    let editor = cx.add_window(|window, cx| {
 4715        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4716        build_editor(buffer, window, cx)
 4717    });
 4718    _ = editor.update(cx, |editor, window, cx| {
 4719        editor.change_selections(None, window, cx, |s| {
 4720            s.select_display_ranges([
 4721                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4722                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4723            ])
 4724        });
 4725        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4726        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4727        assert_eq!(
 4728            editor.selections.display_ranges(cx),
 4729            vec![
 4730                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4731                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4732            ]
 4733        );
 4734    });
 4735
 4736    // With `move_upwards` the selections stay in place, except for
 4737    // the lines inserted above them
 4738    let editor = cx.add_window(|window, cx| {
 4739        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4740        build_editor(buffer, window, cx)
 4741    });
 4742    _ = editor.update(cx, |editor, window, cx| {
 4743        editor.change_selections(None, window, cx, |s| {
 4744            s.select_display_ranges([
 4745                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4746                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4747                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4748                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4749            ])
 4750        });
 4751        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4752        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4753        assert_eq!(
 4754            editor.selections.display_ranges(cx),
 4755            vec![
 4756                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4757                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4758                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4759                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4760            ]
 4761        );
 4762    });
 4763
 4764    let editor = cx.add_window(|window, cx| {
 4765        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4766        build_editor(buffer, window, cx)
 4767    });
 4768    _ = editor.update(cx, |editor, window, cx| {
 4769        editor.change_selections(None, window, cx, |s| {
 4770            s.select_display_ranges([
 4771                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4772                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4773            ])
 4774        });
 4775        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4776        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4777        assert_eq!(
 4778            editor.selections.display_ranges(cx),
 4779            vec![
 4780                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4781                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4782            ]
 4783        );
 4784    });
 4785
 4786    let editor = cx.add_window(|window, cx| {
 4787        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4788        build_editor(buffer, window, cx)
 4789    });
 4790    _ = editor.update(cx, |editor, window, cx| {
 4791        editor.change_selections(None, window, cx, |s| {
 4792            s.select_display_ranges([
 4793                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4794                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4795            ])
 4796        });
 4797        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4798        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4799        assert_eq!(
 4800            editor.selections.display_ranges(cx),
 4801            vec![
 4802                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4803                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4804            ]
 4805        );
 4806    });
 4807}
 4808
 4809#[gpui::test]
 4810fn test_move_line_up_down(cx: &mut TestAppContext) {
 4811    init_test(cx, |_| {});
 4812
 4813    let editor = cx.add_window(|window, cx| {
 4814        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4815        build_editor(buffer, window, cx)
 4816    });
 4817    _ = editor.update(cx, |editor, window, cx| {
 4818        editor.fold_creases(
 4819            vec![
 4820                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4821                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4822                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4823            ],
 4824            true,
 4825            window,
 4826            cx,
 4827        );
 4828        editor.change_selections(None, window, cx, |s| {
 4829            s.select_display_ranges([
 4830                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4831                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4832                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4833                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4834            ])
 4835        });
 4836        assert_eq!(
 4837            editor.display_text(cx),
 4838            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4839        );
 4840
 4841        editor.move_line_up(&MoveLineUp, window, cx);
 4842        assert_eq!(
 4843            editor.display_text(cx),
 4844            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4845        );
 4846        assert_eq!(
 4847            editor.selections.display_ranges(cx),
 4848            vec![
 4849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4850                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4851                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4852                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4853            ]
 4854        );
 4855    });
 4856
 4857    _ = editor.update(cx, |editor, window, cx| {
 4858        editor.move_line_down(&MoveLineDown, window, cx);
 4859        assert_eq!(
 4860            editor.display_text(cx),
 4861            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4862        );
 4863        assert_eq!(
 4864            editor.selections.display_ranges(cx),
 4865            vec![
 4866                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4867                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4868                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4869                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4870            ]
 4871        );
 4872    });
 4873
 4874    _ = editor.update(cx, |editor, window, cx| {
 4875        editor.move_line_down(&MoveLineDown, window, cx);
 4876        assert_eq!(
 4877            editor.display_text(cx),
 4878            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4879        );
 4880        assert_eq!(
 4881            editor.selections.display_ranges(cx),
 4882            vec![
 4883                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4884                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4885                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4886                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4887            ]
 4888        );
 4889    });
 4890
 4891    _ = editor.update(cx, |editor, window, cx| {
 4892        editor.move_line_up(&MoveLineUp, window, cx);
 4893        assert_eq!(
 4894            editor.display_text(cx),
 4895            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4896        );
 4897        assert_eq!(
 4898            editor.selections.display_ranges(cx),
 4899            vec![
 4900                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4901                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4902                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4903                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4904            ]
 4905        );
 4906    });
 4907}
 4908
 4909#[gpui::test]
 4910fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4911    init_test(cx, |_| {});
 4912
 4913    let editor = cx.add_window(|window, cx| {
 4914        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4915        build_editor(buffer, window, cx)
 4916    });
 4917    _ = editor.update(cx, |editor, window, cx| {
 4918        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4919        editor.insert_blocks(
 4920            [BlockProperties {
 4921                style: BlockStyle::Fixed,
 4922                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4923                height: Some(1),
 4924                render: Arc::new(|_| div().into_any()),
 4925                priority: 0,
 4926                render_in_minimap: true,
 4927            }],
 4928            Some(Autoscroll::fit()),
 4929            cx,
 4930        );
 4931        editor.change_selections(None, window, cx, |s| {
 4932            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4933        });
 4934        editor.move_line_down(&MoveLineDown, window, cx);
 4935    });
 4936}
 4937
 4938#[gpui::test]
 4939async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4940    init_test(cx, |_| {});
 4941
 4942    let mut cx = EditorTestContext::new(cx).await;
 4943    cx.set_state(
 4944        &"
 4945            ˇzero
 4946            one
 4947            two
 4948            three
 4949            four
 4950            five
 4951        "
 4952        .unindent(),
 4953    );
 4954
 4955    // Create a four-line block that replaces three lines of text.
 4956    cx.update_editor(|editor, window, cx| {
 4957        let snapshot = editor.snapshot(window, cx);
 4958        let snapshot = &snapshot.buffer_snapshot;
 4959        let placement = BlockPlacement::Replace(
 4960            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4961        );
 4962        editor.insert_blocks(
 4963            [BlockProperties {
 4964                placement,
 4965                height: Some(4),
 4966                style: BlockStyle::Sticky,
 4967                render: Arc::new(|_| gpui::div().into_any_element()),
 4968                priority: 0,
 4969                render_in_minimap: true,
 4970            }],
 4971            None,
 4972            cx,
 4973        );
 4974    });
 4975
 4976    // Move down so that the cursor touches the block.
 4977    cx.update_editor(|editor, window, cx| {
 4978        editor.move_down(&Default::default(), window, cx);
 4979    });
 4980    cx.assert_editor_state(
 4981        &"
 4982            zero
 4983            «one
 4984            two
 4985            threeˇ»
 4986            four
 4987            five
 4988        "
 4989        .unindent(),
 4990    );
 4991
 4992    // Move down past the block.
 4993    cx.update_editor(|editor, window, cx| {
 4994        editor.move_down(&Default::default(), window, cx);
 4995    });
 4996    cx.assert_editor_state(
 4997        &"
 4998            zero
 4999            one
 5000            two
 5001            three
 5002            ˇfour
 5003            five
 5004        "
 5005        .unindent(),
 5006    );
 5007}
 5008
 5009#[gpui::test]
 5010fn test_transpose(cx: &mut TestAppContext) {
 5011    init_test(cx, |_| {});
 5012
 5013    _ = cx.add_window(|window, cx| {
 5014        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5015        editor.set_style(EditorStyle::default(), window, cx);
 5016        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 5017        editor.transpose(&Default::default(), window, cx);
 5018        assert_eq!(editor.text(cx), "bac");
 5019        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5020
 5021        editor.transpose(&Default::default(), window, cx);
 5022        assert_eq!(editor.text(cx), "bca");
 5023        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5024
 5025        editor.transpose(&Default::default(), window, cx);
 5026        assert_eq!(editor.text(cx), "bac");
 5027        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5028
 5029        editor
 5030    });
 5031
 5032    _ = cx.add_window(|window, cx| {
 5033        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5034        editor.set_style(EditorStyle::default(), window, cx);
 5035        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 5036        editor.transpose(&Default::default(), window, cx);
 5037        assert_eq!(editor.text(cx), "acb\nde");
 5038        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5039
 5040        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 5041        editor.transpose(&Default::default(), window, cx);
 5042        assert_eq!(editor.text(cx), "acbd\ne");
 5043        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5044
 5045        editor.transpose(&Default::default(), window, cx);
 5046        assert_eq!(editor.text(cx), "acbde\n");
 5047        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5048
 5049        editor.transpose(&Default::default(), window, cx);
 5050        assert_eq!(editor.text(cx), "acbd\ne");
 5051        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5052
 5053        editor
 5054    });
 5055
 5056    _ = cx.add_window(|window, cx| {
 5057        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5058        editor.set_style(EditorStyle::default(), window, cx);
 5059        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 5060        editor.transpose(&Default::default(), window, cx);
 5061        assert_eq!(editor.text(cx), "bacd\ne");
 5062        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5063
 5064        editor.transpose(&Default::default(), window, cx);
 5065        assert_eq!(editor.text(cx), "bcade\n");
 5066        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5067
 5068        editor.transpose(&Default::default(), window, cx);
 5069        assert_eq!(editor.text(cx), "bcda\ne");
 5070        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5071
 5072        editor.transpose(&Default::default(), window, cx);
 5073        assert_eq!(editor.text(cx), "bcade\n");
 5074        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5075
 5076        editor.transpose(&Default::default(), window, cx);
 5077        assert_eq!(editor.text(cx), "bcaed\n");
 5078        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5079
 5080        editor
 5081    });
 5082
 5083    _ = cx.add_window(|window, cx| {
 5084        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5085        editor.set_style(EditorStyle::default(), window, cx);
 5086        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 5087        editor.transpose(&Default::default(), window, cx);
 5088        assert_eq!(editor.text(cx), "🏀🍐✋");
 5089        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5090
 5091        editor.transpose(&Default::default(), window, cx);
 5092        assert_eq!(editor.text(cx), "🏀✋🍐");
 5093        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5094
 5095        editor.transpose(&Default::default(), window, cx);
 5096        assert_eq!(editor.text(cx), "🏀🍐✋");
 5097        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5098
 5099        editor
 5100    });
 5101}
 5102
 5103#[gpui::test]
 5104async fn test_rewrap(cx: &mut TestAppContext) {
 5105    init_test(cx, |settings| {
 5106        settings.languages.extend([
 5107            (
 5108                "Markdown".into(),
 5109                LanguageSettingsContent {
 5110                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5111                    ..Default::default()
 5112                },
 5113            ),
 5114            (
 5115                "Plain Text".into(),
 5116                LanguageSettingsContent {
 5117                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5118                    ..Default::default()
 5119                },
 5120            ),
 5121        ])
 5122    });
 5123
 5124    let mut cx = EditorTestContext::new(cx).await;
 5125
 5126    let language_with_c_comments = Arc::new(Language::new(
 5127        LanguageConfig {
 5128            line_comments: vec!["// ".into()],
 5129            ..LanguageConfig::default()
 5130        },
 5131        None,
 5132    ));
 5133    let language_with_pound_comments = Arc::new(Language::new(
 5134        LanguageConfig {
 5135            line_comments: vec!["# ".into()],
 5136            ..LanguageConfig::default()
 5137        },
 5138        None,
 5139    ));
 5140    let markdown_language = Arc::new(Language::new(
 5141        LanguageConfig {
 5142            name: "Markdown".into(),
 5143            ..LanguageConfig::default()
 5144        },
 5145        None,
 5146    ));
 5147    let language_with_doc_comments = Arc::new(Language::new(
 5148        LanguageConfig {
 5149            line_comments: vec!["// ".into(), "/// ".into()],
 5150            ..LanguageConfig::default()
 5151        },
 5152        Some(tree_sitter_rust::LANGUAGE.into()),
 5153    ));
 5154
 5155    let plaintext_language = Arc::new(Language::new(
 5156        LanguageConfig {
 5157            name: "Plain Text".into(),
 5158            ..LanguageConfig::default()
 5159        },
 5160        None,
 5161    ));
 5162
 5163    assert_rewrap(
 5164        indoc! {"
 5165            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5166        "},
 5167        indoc! {"
 5168            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5169            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5170            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 5171            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5172            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 5173            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5174            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5175            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5176            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5177            // porttitor id. Aliquam id accumsan eros.
 5178        "},
 5179        language_with_c_comments.clone(),
 5180        &mut cx,
 5181    );
 5182
 5183    // Test that rewrapping works inside of a selection
 5184    assert_rewrap(
 5185        indoc! {"
 5186            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
 5187        "},
 5188        indoc! {"
 5189            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5190            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5191            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 5192            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5193            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 5194            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5195            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5196            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5197            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5198            // porttitor id. Aliquam id accumsan eros.ˇ»
 5199        "},
 5200        language_with_c_comments.clone(),
 5201        &mut cx,
 5202    );
 5203
 5204    // Test that cursors that expand to the same region are collapsed.
 5205    assert_rewrap(
 5206        indoc! {"
 5207            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5208            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5209            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5210            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5211        "},
 5212        indoc! {"
 5213            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5214            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5215            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 5216            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5217            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5218            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5219            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5220            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5221            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5222            // porttitor id. Aliquam id accumsan eros.
 5223        "},
 5224        language_with_c_comments.clone(),
 5225        &mut cx,
 5226    );
 5227
 5228    // Test that non-contiguous selections are treated separately.
 5229    assert_rewrap(
 5230        indoc! {"
 5231            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5232            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5233            //
 5234            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5235            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5236        "},
 5237        indoc! {"
 5238            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5239            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5240            // auctor, eu lacinia sapien scelerisque.
 5241            //
 5242            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5243            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5244            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5245            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5246            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5247            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5248            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5249        "},
 5250        language_with_c_comments.clone(),
 5251        &mut cx,
 5252    );
 5253
 5254    // Test that different comment prefixes are supported.
 5255    assert_rewrap(
 5256        indoc! {"
 5257            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 5258        "},
 5259        indoc! {"
 5260            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5261            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5262            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5263            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5264            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5265            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5266            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5267            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5268            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5269            # accumsan eros.
 5270        "},
 5271        language_with_pound_comments.clone(),
 5272        &mut cx,
 5273    );
 5274
 5275    // Test that rewrapping is ignored outside of comments in most languages.
 5276    assert_rewrap(
 5277        indoc! {"
 5278            /// Adds two numbers.
 5279            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5280            fn add(a: u32, b: u32) -> u32 {
 5281                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5282            }
 5283        "},
 5284        indoc! {"
 5285            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5286            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5287            fn add(a: u32, b: u32) -> u32 {
 5288                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 5289            }
 5290        "},
 5291        language_with_doc_comments.clone(),
 5292        &mut cx,
 5293    );
 5294
 5295    // Test that rewrapping works in Markdown and Plain Text languages.
 5296    assert_rewrap(
 5297        indoc! {"
 5298            # Hello
 5299
 5300            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5301        "},
 5302        indoc! {"
 5303            # Hello
 5304
 5305            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5306            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5307            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5308            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5309            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5310            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5311            Integer sit amet scelerisque nisi.
 5312        "},
 5313        markdown_language,
 5314        &mut cx,
 5315    );
 5316
 5317    assert_rewrap(
 5318        indoc! {"
 5319            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 5320        "},
 5321        indoc! {"
 5322            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5323            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5324            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5325            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5326            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5327            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5328            Integer sit amet scelerisque nisi.
 5329        "},
 5330        plaintext_language.clone(),
 5331        &mut cx,
 5332    );
 5333
 5334    // Test rewrapping unaligned comments in a selection.
 5335    assert_rewrap(
 5336        indoc! {"
 5337            fn foo() {
 5338                if true {
 5339            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5340            // Praesent semper egestas tellus id dignissim.ˇ»
 5341                    do_something();
 5342                } else {
 5343                    //
 5344                }
 5345            }
 5346        "},
 5347        indoc! {"
 5348            fn foo() {
 5349                if true {
 5350            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5351                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5352                    // egestas tellus id dignissim.ˇ»
 5353                    do_something();
 5354                } else {
 5355                    //
 5356                }
 5357            }
 5358        "},
 5359        language_with_doc_comments.clone(),
 5360        &mut cx,
 5361    );
 5362
 5363    assert_rewrap(
 5364        indoc! {"
 5365            fn foo() {
 5366                if true {
 5367            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5368            // Praesent semper egestas tellus id dignissim.»
 5369                    do_something();
 5370                } else {
 5371                    //
 5372                }
 5373
 5374            }
 5375        "},
 5376        indoc! {"
 5377            fn foo() {
 5378                if true {
 5379            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5380                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5381                    // egestas tellus id dignissim.»
 5382                    do_something();
 5383                } else {
 5384                    //
 5385                }
 5386
 5387            }
 5388        "},
 5389        language_with_doc_comments.clone(),
 5390        &mut cx,
 5391    );
 5392
 5393    assert_rewrap(
 5394        indoc! {"
 5395            «ˇone one one one one one one one one one one one one one one one one one one one one one one one one
 5396
 5397            two»
 5398
 5399            three
 5400
 5401            «ˇ\t
 5402
 5403            four four four four four four four four four four four four four four four four four four four four»
 5404
 5405            «ˇfive five five five five five five five five five five five five five five five five five five five
 5406            \t»
 5407            six six six six six six six six six six six six six six six six six six six six six six six six six
 5408        "},
 5409        indoc! {"
 5410            «ˇone one one one one one one one one one one one one one one one one one one one
 5411            one one one one one
 5412
 5413            two»
 5414
 5415            three
 5416
 5417            «ˇ\t
 5418
 5419            four four four four four four four four four four four four four four four four
 5420            four four four four»
 5421
 5422            «ˇfive five five five five five five five five five five five five five five five
 5423            five five five five
 5424            \t»
 5425            six six six six six six six six six six six six six six six six six six six six six six six six six
 5426        "},
 5427        plaintext_language.clone(),
 5428        &mut cx,
 5429    );
 5430
 5431    assert_rewrap(
 5432        indoc! {"
 5433            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5434            //ˇ
 5435            //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long
 5436            //ˇ short short short
 5437            int main(void) {
 5438                return 17;
 5439            }
 5440        "},
 5441        indoc! {"
 5442            //ˇ long long long long long long long long long long long long long long long
 5443            // long long long long long long long long long long long long long
 5444            //ˇ
 5445            //ˇ long long long long long long long long long long long long long long long
 5446            //ˇ long long long long long long long long long long long long long short short
 5447            // short
 5448            int main(void) {
 5449                return 17;
 5450            }
 5451        "},
 5452        language_with_c_comments,
 5453        &mut cx,
 5454    );
 5455
 5456    #[track_caller]
 5457    fn assert_rewrap(
 5458        unwrapped_text: &str,
 5459        wrapped_text: &str,
 5460        language: Arc<Language>,
 5461        cx: &mut EditorTestContext,
 5462    ) {
 5463        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5464        cx.set_state(unwrapped_text);
 5465        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5466        cx.assert_editor_state(wrapped_text);
 5467    }
 5468}
 5469
 5470#[gpui::test]
 5471async fn test_hard_wrap(cx: &mut TestAppContext) {
 5472    init_test(cx, |_| {});
 5473    let mut cx = EditorTestContext::new(cx).await;
 5474
 5475    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5476    cx.update_editor(|editor, _, cx| {
 5477        editor.set_hard_wrap(Some(14), cx);
 5478    });
 5479
 5480    cx.set_state(indoc!(
 5481        "
 5482        one two three ˇ
 5483        "
 5484    ));
 5485    cx.simulate_input("four");
 5486    cx.run_until_parked();
 5487
 5488    cx.assert_editor_state(indoc!(
 5489        "
 5490        one two three
 5491        fourˇ
 5492        "
 5493    ));
 5494
 5495    cx.update_editor(|editor, window, cx| {
 5496        editor.newline(&Default::default(), window, cx);
 5497    });
 5498    cx.run_until_parked();
 5499    cx.assert_editor_state(indoc!(
 5500        "
 5501        one two three
 5502        four
 5503        ˇ
 5504        "
 5505    ));
 5506
 5507    cx.simulate_input("five");
 5508    cx.run_until_parked();
 5509    cx.assert_editor_state(indoc!(
 5510        "
 5511        one two three
 5512        four
 5513        fiveˇ
 5514        "
 5515    ));
 5516
 5517    cx.update_editor(|editor, window, cx| {
 5518        editor.newline(&Default::default(), window, cx);
 5519    });
 5520    cx.run_until_parked();
 5521    cx.simulate_input("# ");
 5522    cx.run_until_parked();
 5523    cx.assert_editor_state(indoc!(
 5524        "
 5525        one two three
 5526        four
 5527        five
 5528        # ˇ
 5529        "
 5530    ));
 5531
 5532    cx.update_editor(|editor, window, cx| {
 5533        editor.newline(&Default::default(), window, cx);
 5534    });
 5535    cx.run_until_parked();
 5536    cx.assert_editor_state(indoc!(
 5537        "
 5538        one two three
 5539        four
 5540        five
 5541        #\x20
 5542 5543        "
 5544    ));
 5545
 5546    cx.simulate_input(" 6");
 5547    cx.run_until_parked();
 5548    cx.assert_editor_state(indoc!(
 5549        "
 5550        one two three
 5551        four
 5552        five
 5553        #
 5554        # 6ˇ
 5555        "
 5556    ));
 5557}
 5558
 5559#[gpui::test]
 5560async fn test_clipboard(cx: &mut TestAppContext) {
 5561    init_test(cx, |_| {});
 5562
 5563    let mut cx = EditorTestContext::new(cx).await;
 5564
 5565    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5566    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5567    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5568
 5569    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5570    cx.set_state("two ˇfour ˇsix ˇ");
 5571    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5572    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5573
 5574    // Paste again but with only two cursors. Since the number of cursors doesn't
 5575    // match the number of slices in the clipboard, the entire clipboard text
 5576    // is pasted at each cursor.
 5577    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5578    cx.update_editor(|e, window, cx| {
 5579        e.handle_input("( ", window, cx);
 5580        e.paste(&Paste, window, cx);
 5581        e.handle_input(") ", window, cx);
 5582    });
 5583    cx.assert_editor_state(
 5584        &([
 5585            "( one✅ ",
 5586            "three ",
 5587            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5588            "three ",
 5589            "five ) ˇ",
 5590        ]
 5591        .join("\n")),
 5592    );
 5593
 5594    // Cut with three selections, one of which is full-line.
 5595    cx.set_state(indoc! {"
 5596        1«2ˇ»3
 5597        4ˇ567
 5598        «8ˇ»9"});
 5599    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5600    cx.assert_editor_state(indoc! {"
 5601        1ˇ3
 5602        ˇ9"});
 5603
 5604    // Paste with three selections, noticing how the copied selection that was full-line
 5605    // gets inserted before the second cursor.
 5606    cx.set_state(indoc! {"
 5607        1ˇ3
 5608 5609        «oˇ»ne"});
 5610    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5611    cx.assert_editor_state(indoc! {"
 5612        12ˇ3
 5613        4567
 5614 5615        8ˇne"});
 5616
 5617    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5618    cx.set_state(indoc! {"
 5619        The quick brown
 5620        fox juˇmps over
 5621        the lazy dog"});
 5622    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5623    assert_eq!(
 5624        cx.read_from_clipboard()
 5625            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5626        Some("fox jumps over\n".to_string())
 5627    );
 5628
 5629    // Paste with three selections, noticing how the copied full-line selection is inserted
 5630    // before the empty selections but replaces the selection that is non-empty.
 5631    cx.set_state(indoc! {"
 5632        Tˇhe quick brown
 5633        «foˇ»x jumps over
 5634        tˇhe lazy dog"});
 5635    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5636    cx.assert_editor_state(indoc! {"
 5637        fox jumps over
 5638        Tˇhe quick brown
 5639        fox jumps over
 5640        ˇx jumps over
 5641        fox jumps over
 5642        tˇhe lazy dog"});
 5643}
 5644
 5645#[gpui::test]
 5646async fn test_copy_trim(cx: &mut TestAppContext) {
 5647    init_test(cx, |_| {});
 5648
 5649    let mut cx = EditorTestContext::new(cx).await;
 5650    cx.set_state(
 5651        r#"            «for selection in selections.iter() {
 5652            let mut start = selection.start;
 5653            let mut end = selection.end;
 5654            let is_entire_line = selection.is_empty();
 5655            if is_entire_line {
 5656                start = Point::new(start.row, 0);ˇ»
 5657                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5658            }
 5659        "#,
 5660    );
 5661    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5662    assert_eq!(
 5663        cx.read_from_clipboard()
 5664            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5665        Some(
 5666            "for selection in selections.iter() {
 5667            let mut start = selection.start;
 5668            let mut end = selection.end;
 5669            let is_entire_line = selection.is_empty();
 5670            if is_entire_line {
 5671                start = Point::new(start.row, 0);"
 5672                .to_string()
 5673        ),
 5674        "Regular copying preserves all indentation selected",
 5675    );
 5676    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5677    assert_eq!(
 5678        cx.read_from_clipboard()
 5679            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5680        Some(
 5681            "for selection in selections.iter() {
 5682let mut start = selection.start;
 5683let mut end = selection.end;
 5684let is_entire_line = selection.is_empty();
 5685if is_entire_line {
 5686    start = Point::new(start.row, 0);"
 5687                .to_string()
 5688        ),
 5689        "Copying with stripping should strip all leading whitespaces"
 5690    );
 5691
 5692    cx.set_state(
 5693        r#"       «     for selection in selections.iter() {
 5694            let mut start = selection.start;
 5695            let mut end = selection.end;
 5696            let is_entire_line = selection.is_empty();
 5697            if is_entire_line {
 5698                start = Point::new(start.row, 0);ˇ»
 5699                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5700            }
 5701        "#,
 5702    );
 5703    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5704    assert_eq!(
 5705        cx.read_from_clipboard()
 5706            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5707        Some(
 5708            "     for selection in selections.iter() {
 5709            let mut start = selection.start;
 5710            let mut end = selection.end;
 5711            let is_entire_line = selection.is_empty();
 5712            if is_entire_line {
 5713                start = Point::new(start.row, 0);"
 5714                .to_string()
 5715        ),
 5716        "Regular copying preserves all indentation selected",
 5717    );
 5718    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5719    assert_eq!(
 5720        cx.read_from_clipboard()
 5721            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5722        Some(
 5723            "for selection in selections.iter() {
 5724let mut start = selection.start;
 5725let mut end = selection.end;
 5726let is_entire_line = selection.is_empty();
 5727if is_entire_line {
 5728    start = Point::new(start.row, 0);"
 5729                .to_string()
 5730        ),
 5731        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5732    );
 5733
 5734    cx.set_state(
 5735        r#"       «ˇ     for selection in selections.iter() {
 5736            let mut start = selection.start;
 5737            let mut end = selection.end;
 5738            let is_entire_line = selection.is_empty();
 5739            if is_entire_line {
 5740                start = Point::new(start.row, 0);»
 5741                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5742            }
 5743        "#,
 5744    );
 5745    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5746    assert_eq!(
 5747        cx.read_from_clipboard()
 5748            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5749        Some(
 5750            "     for selection in selections.iter() {
 5751            let mut start = selection.start;
 5752            let mut end = selection.end;
 5753            let is_entire_line = selection.is_empty();
 5754            if is_entire_line {
 5755                start = Point::new(start.row, 0);"
 5756                .to_string()
 5757        ),
 5758        "Regular copying for reverse selection works the same",
 5759    );
 5760    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5761    assert_eq!(
 5762        cx.read_from_clipboard()
 5763            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5764        Some(
 5765            "for selection in selections.iter() {
 5766let mut start = selection.start;
 5767let mut end = selection.end;
 5768let is_entire_line = selection.is_empty();
 5769if is_entire_line {
 5770    start = Point::new(start.row, 0);"
 5771                .to_string()
 5772        ),
 5773        "Copying with stripping for reverse selection works the same"
 5774    );
 5775
 5776    cx.set_state(
 5777        r#"            for selection «in selections.iter() {
 5778            let mut start = selection.start;
 5779            let mut end = selection.end;
 5780            let is_entire_line = selection.is_empty();
 5781            if is_entire_line {
 5782                start = Point::new(start.row, 0);ˇ»
 5783                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5784            }
 5785        "#,
 5786    );
 5787    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5788    assert_eq!(
 5789        cx.read_from_clipboard()
 5790            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5791        Some(
 5792            "in selections.iter() {
 5793            let mut start = selection.start;
 5794            let mut end = selection.end;
 5795            let is_entire_line = selection.is_empty();
 5796            if is_entire_line {
 5797                start = Point::new(start.row, 0);"
 5798                .to_string()
 5799        ),
 5800        "When selecting past the indent, the copying works as usual",
 5801    );
 5802    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5803    assert_eq!(
 5804        cx.read_from_clipboard()
 5805            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5806        Some(
 5807            "in selections.iter() {
 5808            let mut start = selection.start;
 5809            let mut end = selection.end;
 5810            let is_entire_line = selection.is_empty();
 5811            if is_entire_line {
 5812                start = Point::new(start.row, 0);"
 5813                .to_string()
 5814        ),
 5815        "When selecting past the indent, nothing is trimmed"
 5816    );
 5817
 5818    cx.set_state(
 5819        r#"            «for selection in selections.iter() {
 5820            let mut start = selection.start;
 5821
 5822            let mut end = selection.end;
 5823            let is_entire_line = selection.is_empty();
 5824            if is_entire_line {
 5825                start = Point::new(start.row, 0);
 5826ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5827            }
 5828        "#,
 5829    );
 5830    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5831    assert_eq!(
 5832        cx.read_from_clipboard()
 5833            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5834        Some(
 5835            "for selection in selections.iter() {
 5836let mut start = selection.start;
 5837
 5838let mut end = selection.end;
 5839let is_entire_line = selection.is_empty();
 5840if is_entire_line {
 5841    start = Point::new(start.row, 0);
 5842"
 5843            .to_string()
 5844        ),
 5845        "Copying with stripping should ignore empty lines"
 5846    );
 5847}
 5848
 5849#[gpui::test]
 5850async fn test_paste_multiline(cx: &mut TestAppContext) {
 5851    init_test(cx, |_| {});
 5852
 5853    let mut cx = EditorTestContext::new(cx).await;
 5854    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5855
 5856    // Cut an indented block, without the leading whitespace.
 5857    cx.set_state(indoc! {"
 5858        const a: B = (
 5859            c(),
 5860            «d(
 5861                e,
 5862                f
 5863            )ˇ»
 5864        );
 5865    "});
 5866    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5867    cx.assert_editor_state(indoc! {"
 5868        const a: B = (
 5869            c(),
 5870            ˇ
 5871        );
 5872    "});
 5873
 5874    // Paste it at the same position.
 5875    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5876    cx.assert_editor_state(indoc! {"
 5877        const a: B = (
 5878            c(),
 5879            d(
 5880                e,
 5881                f
 5882 5883        );
 5884    "});
 5885
 5886    // Paste it at a line with a lower indent level.
 5887    cx.set_state(indoc! {"
 5888        ˇ
 5889        const a: B = (
 5890            c(),
 5891        );
 5892    "});
 5893    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5894    cx.assert_editor_state(indoc! {"
 5895        d(
 5896            e,
 5897            f
 5898 5899        const a: B = (
 5900            c(),
 5901        );
 5902    "});
 5903
 5904    // Cut an indented block, with the leading whitespace.
 5905    cx.set_state(indoc! {"
 5906        const a: B = (
 5907            c(),
 5908        «    d(
 5909                e,
 5910                f
 5911            )
 5912        ˇ»);
 5913    "});
 5914    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5915    cx.assert_editor_state(indoc! {"
 5916        const a: B = (
 5917            c(),
 5918        ˇ);
 5919    "});
 5920
 5921    // Paste it at the same position.
 5922    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5923    cx.assert_editor_state(indoc! {"
 5924        const a: B = (
 5925            c(),
 5926            d(
 5927                e,
 5928                f
 5929            )
 5930        ˇ);
 5931    "});
 5932
 5933    // Paste it at a line with a higher indent level.
 5934    cx.set_state(indoc! {"
 5935        const a: B = (
 5936            c(),
 5937            d(
 5938                e,
 5939 5940            )
 5941        );
 5942    "});
 5943    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5944    cx.assert_editor_state(indoc! {"
 5945        const a: B = (
 5946            c(),
 5947            d(
 5948                e,
 5949                f    d(
 5950                    e,
 5951                    f
 5952                )
 5953        ˇ
 5954            )
 5955        );
 5956    "});
 5957
 5958    // Copy an indented block, starting mid-line
 5959    cx.set_state(indoc! {"
 5960        const a: B = (
 5961            c(),
 5962            somethin«g(
 5963                e,
 5964                f
 5965            )ˇ»
 5966        );
 5967    "});
 5968    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5969
 5970    // Paste it on a line with a lower indent level
 5971    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5972    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5973    cx.assert_editor_state(indoc! {"
 5974        const a: B = (
 5975            c(),
 5976            something(
 5977                e,
 5978                f
 5979            )
 5980        );
 5981        g(
 5982            e,
 5983            f
 5984"});
 5985}
 5986
 5987#[gpui::test]
 5988async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5989    init_test(cx, |_| {});
 5990
 5991    cx.write_to_clipboard(ClipboardItem::new_string(
 5992        "    d(\n        e\n    );\n".into(),
 5993    ));
 5994
 5995    let mut cx = EditorTestContext::new(cx).await;
 5996    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5997
 5998    cx.set_state(indoc! {"
 5999        fn a() {
 6000            b();
 6001            if c() {
 6002                ˇ
 6003            }
 6004        }
 6005    "});
 6006
 6007    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6008    cx.assert_editor_state(indoc! {"
 6009        fn a() {
 6010            b();
 6011            if c() {
 6012                d(
 6013                    e
 6014                );
 6015        ˇ
 6016            }
 6017        }
 6018    "});
 6019
 6020    cx.set_state(indoc! {"
 6021        fn a() {
 6022            b();
 6023            ˇ
 6024        }
 6025    "});
 6026
 6027    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6028    cx.assert_editor_state(indoc! {"
 6029        fn a() {
 6030            b();
 6031            d(
 6032                e
 6033            );
 6034        ˇ
 6035        }
 6036    "});
 6037}
 6038
 6039#[gpui::test]
 6040fn test_select_all(cx: &mut TestAppContext) {
 6041    init_test(cx, |_| {});
 6042
 6043    let editor = cx.add_window(|window, cx| {
 6044        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6045        build_editor(buffer, window, cx)
 6046    });
 6047    _ = editor.update(cx, |editor, window, cx| {
 6048        editor.select_all(&SelectAll, window, cx);
 6049        assert_eq!(
 6050            editor.selections.display_ranges(cx),
 6051            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6052        );
 6053    });
 6054}
 6055
 6056#[gpui::test]
 6057fn test_select_line(cx: &mut TestAppContext) {
 6058    init_test(cx, |_| {});
 6059
 6060    let editor = cx.add_window(|window, cx| {
 6061        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6062        build_editor(buffer, window, cx)
 6063    });
 6064    _ = editor.update(cx, |editor, window, cx| {
 6065        editor.change_selections(None, window, cx, |s| {
 6066            s.select_display_ranges([
 6067                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6068                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6069                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6070                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6071            ])
 6072        });
 6073        editor.select_line(&SelectLine, window, cx);
 6074        assert_eq!(
 6075            editor.selections.display_ranges(cx),
 6076            vec![
 6077                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6078                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6079            ]
 6080        );
 6081    });
 6082
 6083    _ = editor.update(cx, |editor, window, cx| {
 6084        editor.select_line(&SelectLine, window, cx);
 6085        assert_eq!(
 6086            editor.selections.display_ranges(cx),
 6087            vec![
 6088                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6089                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6090            ]
 6091        );
 6092    });
 6093
 6094    _ = editor.update(cx, |editor, window, cx| {
 6095        editor.select_line(&SelectLine, window, cx);
 6096        assert_eq!(
 6097            editor.selections.display_ranges(cx),
 6098            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6099        );
 6100    });
 6101}
 6102
 6103#[gpui::test]
 6104async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6105    init_test(cx, |_| {});
 6106    let mut cx = EditorTestContext::new(cx).await;
 6107
 6108    #[track_caller]
 6109    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6110        cx.set_state(initial_state);
 6111        cx.update_editor(|e, window, cx| {
 6112            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6113        });
 6114        cx.assert_editor_state(expected_state);
 6115    }
 6116
 6117    // Selection starts and ends at the middle of lines, left-to-right
 6118    test(
 6119        &mut cx,
 6120        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6121        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6122    );
 6123    // Same thing, right-to-left
 6124    test(
 6125        &mut cx,
 6126        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6127        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6128    );
 6129
 6130    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6131    test(
 6132        &mut cx,
 6133        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6134        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6135    );
 6136    // Same thing, right-to-left
 6137    test(
 6138        &mut cx,
 6139        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6140        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6141    );
 6142
 6143    // Whole buffer, left-to-right, last line ends with newline
 6144    test(
 6145        &mut cx,
 6146        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6147        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6148    );
 6149    // Same thing, right-to-left
 6150    test(
 6151        &mut cx,
 6152        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6153        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6154    );
 6155
 6156    // Starts at the end of a line, ends at the start of another
 6157    test(
 6158        &mut cx,
 6159        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6160        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6161    );
 6162}
 6163
 6164#[gpui::test]
 6165async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6166    init_test(cx, |_| {});
 6167
 6168    let editor = cx.add_window(|window, cx| {
 6169        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6170        build_editor(buffer, window, cx)
 6171    });
 6172
 6173    // setup
 6174    _ = editor.update(cx, |editor, window, cx| {
 6175        editor.fold_creases(
 6176            vec![
 6177                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6178                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6179                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6180            ],
 6181            true,
 6182            window,
 6183            cx,
 6184        );
 6185        assert_eq!(
 6186            editor.display_text(cx),
 6187            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6188        );
 6189    });
 6190
 6191    _ = editor.update(cx, |editor, window, cx| {
 6192        editor.change_selections(None, window, cx, |s| {
 6193            s.select_display_ranges([
 6194                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6195                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6196                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6197                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6198            ])
 6199        });
 6200        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6201        assert_eq!(
 6202            editor.display_text(cx),
 6203            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6204        );
 6205    });
 6206    EditorTestContext::for_editor(editor, cx)
 6207        .await
 6208        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6209
 6210    _ = editor.update(cx, |editor, window, cx| {
 6211        editor.change_selections(None, window, cx, |s| {
 6212            s.select_display_ranges([
 6213                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6214            ])
 6215        });
 6216        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6217        assert_eq!(
 6218            editor.display_text(cx),
 6219            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6220        );
 6221        assert_eq!(
 6222            editor.selections.display_ranges(cx),
 6223            [
 6224                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6225                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6226                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6227                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6228                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6229                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6230                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6231            ]
 6232        );
 6233    });
 6234    EditorTestContext::for_editor(editor, cx)
 6235        .await
 6236        .assert_editor_state(
 6237            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6238        );
 6239}
 6240
 6241#[gpui::test]
 6242async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6243    init_test(cx, |_| {});
 6244
 6245    let mut cx = EditorTestContext::new(cx).await;
 6246
 6247    cx.set_state(indoc!(
 6248        r#"abc
 6249           defˇghi
 6250
 6251           jk
 6252           nlmo
 6253           "#
 6254    ));
 6255
 6256    cx.update_editor(|editor, window, cx| {
 6257        editor.add_selection_above(&Default::default(), window, cx);
 6258    });
 6259
 6260    cx.assert_editor_state(indoc!(
 6261        r#"abcˇ
 6262           defˇghi
 6263
 6264           jk
 6265           nlmo
 6266           "#
 6267    ));
 6268
 6269    cx.update_editor(|editor, window, cx| {
 6270        editor.add_selection_above(&Default::default(), window, cx);
 6271    });
 6272
 6273    cx.assert_editor_state(indoc!(
 6274        r#"abcˇ
 6275            defˇghi
 6276
 6277            jk
 6278            nlmo
 6279            "#
 6280    ));
 6281
 6282    cx.update_editor(|editor, window, cx| {
 6283        editor.add_selection_below(&Default::default(), window, cx);
 6284    });
 6285
 6286    cx.assert_editor_state(indoc!(
 6287        r#"abc
 6288           defˇghi
 6289
 6290           jk
 6291           nlmo
 6292           "#
 6293    ));
 6294
 6295    cx.update_editor(|editor, window, cx| {
 6296        editor.undo_selection(&Default::default(), window, cx);
 6297    });
 6298
 6299    cx.assert_editor_state(indoc!(
 6300        r#"abcˇ
 6301           defˇghi
 6302
 6303           jk
 6304           nlmo
 6305           "#
 6306    ));
 6307
 6308    cx.update_editor(|editor, window, cx| {
 6309        editor.redo_selection(&Default::default(), window, cx);
 6310    });
 6311
 6312    cx.assert_editor_state(indoc!(
 6313        r#"abc
 6314           defˇghi
 6315
 6316           jk
 6317           nlmo
 6318           "#
 6319    ));
 6320
 6321    cx.update_editor(|editor, window, cx| {
 6322        editor.add_selection_below(&Default::default(), window, cx);
 6323    });
 6324
 6325    cx.assert_editor_state(indoc!(
 6326        r#"abc
 6327           defˇghi
 6328           ˇ
 6329           jk
 6330           nlmo
 6331           "#
 6332    ));
 6333
 6334    cx.update_editor(|editor, window, cx| {
 6335        editor.add_selection_below(&Default::default(), window, cx);
 6336    });
 6337
 6338    cx.assert_editor_state(indoc!(
 6339        r#"abc
 6340           defˇghi
 6341           ˇ
 6342           jkˇ
 6343           nlmo
 6344           "#
 6345    ));
 6346
 6347    cx.update_editor(|editor, window, cx| {
 6348        editor.add_selection_below(&Default::default(), window, cx);
 6349    });
 6350
 6351    cx.assert_editor_state(indoc!(
 6352        r#"abc
 6353           defˇghi
 6354           ˇ
 6355           jkˇ
 6356           nlmˇo
 6357           "#
 6358    ));
 6359
 6360    cx.update_editor(|editor, window, cx| {
 6361        editor.add_selection_below(&Default::default(), window, cx);
 6362    });
 6363
 6364    cx.assert_editor_state(indoc!(
 6365        r#"abc
 6366           defˇghi
 6367           ˇ
 6368           jkˇ
 6369           nlmˇo
 6370           ˇ"#
 6371    ));
 6372
 6373    // change selections
 6374    cx.set_state(indoc!(
 6375        r#"abc
 6376           def«ˇg»hi
 6377
 6378           jk
 6379           nlmo
 6380           "#
 6381    ));
 6382
 6383    cx.update_editor(|editor, window, cx| {
 6384        editor.add_selection_below(&Default::default(), window, cx);
 6385    });
 6386
 6387    cx.assert_editor_state(indoc!(
 6388        r#"abc
 6389           def«ˇg»hi
 6390
 6391           jk
 6392           nlm«ˇo»
 6393           "#
 6394    ));
 6395
 6396    cx.update_editor(|editor, window, cx| {
 6397        editor.add_selection_below(&Default::default(), window, cx);
 6398    });
 6399
 6400    cx.assert_editor_state(indoc!(
 6401        r#"abc
 6402           def«ˇg»hi
 6403
 6404           jk
 6405           nlm«ˇo»
 6406           "#
 6407    ));
 6408
 6409    cx.update_editor(|editor, window, cx| {
 6410        editor.add_selection_above(&Default::default(), window, cx);
 6411    });
 6412
 6413    cx.assert_editor_state(indoc!(
 6414        r#"abc
 6415           def«ˇg»hi
 6416
 6417           jk
 6418           nlmo
 6419           "#
 6420    ));
 6421
 6422    cx.update_editor(|editor, window, cx| {
 6423        editor.add_selection_above(&Default::default(), window, cx);
 6424    });
 6425
 6426    cx.assert_editor_state(indoc!(
 6427        r#"abc
 6428           def«ˇg»hi
 6429
 6430           jk
 6431           nlmo
 6432           "#
 6433    ));
 6434
 6435    // Change selections again
 6436    cx.set_state(indoc!(
 6437        r#"a«bc
 6438           defgˇ»hi
 6439
 6440           jk
 6441           nlmo
 6442           "#
 6443    ));
 6444
 6445    cx.update_editor(|editor, window, cx| {
 6446        editor.add_selection_below(&Default::default(), window, cx);
 6447    });
 6448
 6449    cx.assert_editor_state(indoc!(
 6450        r#"a«bcˇ»
 6451           d«efgˇ»hi
 6452
 6453           j«kˇ»
 6454           nlmo
 6455           "#
 6456    ));
 6457
 6458    cx.update_editor(|editor, window, cx| {
 6459        editor.add_selection_below(&Default::default(), window, cx);
 6460    });
 6461    cx.assert_editor_state(indoc!(
 6462        r#"a«bcˇ»
 6463           d«efgˇ»hi
 6464
 6465           j«kˇ»
 6466           n«lmoˇ»
 6467           "#
 6468    ));
 6469    cx.update_editor(|editor, window, cx| {
 6470        editor.add_selection_above(&Default::default(), window, cx);
 6471    });
 6472
 6473    cx.assert_editor_state(indoc!(
 6474        r#"a«bcˇ»
 6475           d«efgˇ»hi
 6476
 6477           j«kˇ»
 6478           nlmo
 6479           "#
 6480    ));
 6481
 6482    // Change selections again
 6483    cx.set_state(indoc!(
 6484        r#"abc
 6485           d«ˇefghi
 6486
 6487           jk
 6488           nlm»o
 6489           "#
 6490    ));
 6491
 6492    cx.update_editor(|editor, window, cx| {
 6493        editor.add_selection_above(&Default::default(), window, cx);
 6494    });
 6495
 6496    cx.assert_editor_state(indoc!(
 6497        r#"a«ˇbc»
 6498           d«ˇef»ghi
 6499
 6500           j«ˇk»
 6501           n«ˇlm»o
 6502           "#
 6503    ));
 6504
 6505    cx.update_editor(|editor, window, cx| {
 6506        editor.add_selection_below(&Default::default(), window, cx);
 6507    });
 6508
 6509    cx.assert_editor_state(indoc!(
 6510        r#"abc
 6511           d«ˇef»ghi
 6512
 6513           j«ˇk»
 6514           n«ˇlm»o
 6515           "#
 6516    ));
 6517}
 6518
 6519#[gpui::test]
 6520async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6521    init_test(cx, |_| {});
 6522    let mut cx = EditorTestContext::new(cx).await;
 6523
 6524    cx.set_state(indoc!(
 6525        r#"line onˇe
 6526           liˇne two
 6527           line three
 6528           line four"#
 6529    ));
 6530
 6531    cx.update_editor(|editor, window, cx| {
 6532        editor.add_selection_below(&Default::default(), window, cx);
 6533    });
 6534
 6535    // test multiple cursors expand in the same direction
 6536    cx.assert_editor_state(indoc!(
 6537        r#"line onˇe
 6538           liˇne twˇo
 6539           liˇne three
 6540           line four"#
 6541    ));
 6542
 6543    cx.update_editor(|editor, window, cx| {
 6544        editor.add_selection_below(&Default::default(), window, cx);
 6545    });
 6546
 6547    cx.update_editor(|editor, window, cx| {
 6548        editor.add_selection_below(&Default::default(), window, cx);
 6549    });
 6550
 6551    // test multiple cursors expand below overflow
 6552    cx.assert_editor_state(indoc!(
 6553        r#"line onˇe
 6554           liˇne twˇo
 6555           liˇne thˇree
 6556           liˇne foˇur"#
 6557    ));
 6558
 6559    cx.update_editor(|editor, window, cx| {
 6560        editor.add_selection_above(&Default::default(), window, cx);
 6561    });
 6562
 6563    // test multiple cursors retrieves back correctly
 6564    cx.assert_editor_state(indoc!(
 6565        r#"line onˇe
 6566           liˇne twˇo
 6567           liˇne thˇree
 6568           line four"#
 6569    ));
 6570
 6571    cx.update_editor(|editor, window, cx| {
 6572        editor.add_selection_above(&Default::default(), window, cx);
 6573    });
 6574
 6575    cx.update_editor(|editor, window, cx| {
 6576        editor.add_selection_above(&Default::default(), window, cx);
 6577    });
 6578
 6579    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6580    cx.assert_editor_state(indoc!(
 6581        r#"liˇne onˇe
 6582           liˇne two
 6583           line three
 6584           line four"#
 6585    ));
 6586
 6587    cx.update_editor(|editor, window, cx| {
 6588        editor.undo_selection(&Default::default(), window, cx);
 6589    });
 6590
 6591    // test undo
 6592    cx.assert_editor_state(indoc!(
 6593        r#"line onˇe
 6594           liˇne twˇo
 6595           line three
 6596           line four"#
 6597    ));
 6598
 6599    cx.update_editor(|editor, window, cx| {
 6600        editor.redo_selection(&Default::default(), window, cx);
 6601    });
 6602
 6603    // test redo
 6604    cx.assert_editor_state(indoc!(
 6605        r#"liˇne onˇe
 6606           liˇne two
 6607           line three
 6608           line four"#
 6609    ));
 6610
 6611    cx.set_state(indoc!(
 6612        r#"abcd
 6613           ef«ghˇ»
 6614           ijkl
 6615           «mˇ»nop"#
 6616    ));
 6617
 6618    cx.update_editor(|editor, window, cx| {
 6619        editor.add_selection_above(&Default::default(), window, cx);
 6620    });
 6621
 6622    // test multiple selections expand in the same direction
 6623    cx.assert_editor_state(indoc!(
 6624        r#"ab«cdˇ»
 6625           ef«ghˇ»
 6626           «iˇ»jkl
 6627           «mˇ»nop"#
 6628    ));
 6629
 6630    cx.update_editor(|editor, window, cx| {
 6631        editor.add_selection_above(&Default::default(), window, cx);
 6632    });
 6633
 6634    // test multiple selection upward overflow
 6635    cx.assert_editor_state(indoc!(
 6636        r#"ab«cdˇ»
 6637           «eˇ»f«ghˇ»
 6638           «iˇ»jkl
 6639           «mˇ»nop"#
 6640    ));
 6641
 6642    cx.update_editor(|editor, window, cx| {
 6643        editor.add_selection_below(&Default::default(), window, cx);
 6644    });
 6645
 6646    // test multiple selection retrieves back correctly
 6647    cx.assert_editor_state(indoc!(
 6648        r#"abcd
 6649           ef«ghˇ»
 6650           «iˇ»jkl
 6651           «mˇ»nop"#
 6652    ));
 6653
 6654    cx.update_editor(|editor, window, cx| {
 6655        editor.add_selection_below(&Default::default(), window, cx);
 6656    });
 6657
 6658    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6659    cx.assert_editor_state(indoc!(
 6660        r#"abcd
 6661           ef«ghˇ»
 6662           ij«klˇ»
 6663           «mˇ»nop"#
 6664    ));
 6665
 6666    cx.update_editor(|editor, window, cx| {
 6667        editor.undo_selection(&Default::default(), window, cx);
 6668    });
 6669
 6670    // test undo
 6671    cx.assert_editor_state(indoc!(
 6672        r#"abcd
 6673           ef«ghˇ»
 6674           «iˇ»jkl
 6675           «mˇ»nop"#
 6676    ));
 6677
 6678    cx.update_editor(|editor, window, cx| {
 6679        editor.redo_selection(&Default::default(), window, cx);
 6680    });
 6681
 6682    // test redo
 6683    cx.assert_editor_state(indoc!(
 6684        r#"abcd
 6685           ef«ghˇ»
 6686           ij«klˇ»
 6687           «mˇ»nop"#
 6688    ));
 6689}
 6690
 6691#[gpui::test]
 6692async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6693    init_test(cx, |_| {});
 6694    let mut cx = EditorTestContext::new(cx).await;
 6695
 6696    cx.set_state(indoc!(
 6697        r#"line onˇe
 6698           liˇne two
 6699           line three
 6700           line four"#
 6701    ));
 6702
 6703    cx.update_editor(|editor, window, cx| {
 6704        editor.add_selection_below(&Default::default(), window, cx);
 6705        editor.add_selection_below(&Default::default(), window, cx);
 6706        editor.add_selection_below(&Default::default(), window, cx);
 6707    });
 6708
 6709    // initial state with two multi cursor groups
 6710    cx.assert_editor_state(indoc!(
 6711        r#"line onˇe
 6712           liˇne twˇo
 6713           liˇne thˇree
 6714           liˇne foˇur"#
 6715    ));
 6716
 6717    // add single cursor in middle - simulate opt click
 6718    cx.update_editor(|editor, window, cx| {
 6719        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6720        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6721        editor.end_selection(window, cx);
 6722    });
 6723
 6724    cx.assert_editor_state(indoc!(
 6725        r#"line onˇe
 6726           liˇne twˇo
 6727           liˇneˇ thˇree
 6728           liˇne foˇur"#
 6729    ));
 6730
 6731    cx.update_editor(|editor, window, cx| {
 6732        editor.add_selection_above(&Default::default(), window, cx);
 6733    });
 6734
 6735    // test new added selection expands above and existing selection shrinks
 6736    cx.assert_editor_state(indoc!(
 6737        r#"line onˇe
 6738           liˇneˇ twˇo
 6739           liˇneˇ thˇree
 6740           line four"#
 6741    ));
 6742
 6743    cx.update_editor(|editor, window, cx| {
 6744        editor.add_selection_above(&Default::default(), window, cx);
 6745    });
 6746
 6747    // test new added selection expands above and existing selection shrinks
 6748    cx.assert_editor_state(indoc!(
 6749        r#"lineˇ onˇe
 6750           liˇneˇ twˇo
 6751           lineˇ three
 6752           line four"#
 6753    ));
 6754
 6755    // intial state with two selection groups
 6756    cx.set_state(indoc!(
 6757        r#"abcd
 6758           ef«ghˇ»
 6759           ijkl
 6760           «mˇ»nop"#
 6761    ));
 6762
 6763    cx.update_editor(|editor, window, cx| {
 6764        editor.add_selection_above(&Default::default(), window, cx);
 6765        editor.add_selection_above(&Default::default(), window, cx);
 6766    });
 6767
 6768    cx.assert_editor_state(indoc!(
 6769        r#"ab«cdˇ»
 6770           «eˇ»f«ghˇ»
 6771           «iˇ»jkl
 6772           «mˇ»nop"#
 6773    ));
 6774
 6775    // add single selection in middle - simulate opt drag
 6776    cx.update_editor(|editor, window, cx| {
 6777        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6778        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6779        editor.update_selection(
 6780            DisplayPoint::new(DisplayRow(2), 4),
 6781            0,
 6782            gpui::Point::<f32>::default(),
 6783            window,
 6784            cx,
 6785        );
 6786        editor.end_selection(window, cx);
 6787    });
 6788
 6789    cx.assert_editor_state(indoc!(
 6790        r#"ab«cdˇ»
 6791           «eˇ»f«ghˇ»
 6792           «iˇ»jk«lˇ»
 6793           «mˇ»nop"#
 6794    ));
 6795
 6796    cx.update_editor(|editor, window, cx| {
 6797        editor.add_selection_below(&Default::default(), window, cx);
 6798    });
 6799
 6800    // test new added selection expands below, others shrinks from above
 6801    cx.assert_editor_state(indoc!(
 6802        r#"abcd
 6803           ef«ghˇ»
 6804           «iˇ»jk«lˇ»
 6805           «mˇ»no«pˇ»"#
 6806    ));
 6807}
 6808
 6809#[gpui::test]
 6810async fn test_select_next(cx: &mut TestAppContext) {
 6811    init_test(cx, |_| {});
 6812
 6813    let mut cx = EditorTestContext::new(cx).await;
 6814    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6815
 6816    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6817        .unwrap();
 6818    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6819
 6820    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6821        .unwrap();
 6822    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6823
 6824    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6825    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6826
 6827    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6828    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6829
 6830    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6831        .unwrap();
 6832    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6833
 6834    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6835        .unwrap();
 6836    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6837
 6838    // Test selection direction should be preserved
 6839    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6840
 6841    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6842        .unwrap();
 6843    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6844}
 6845
 6846#[gpui::test]
 6847async fn test_select_all_matches(cx: &mut TestAppContext) {
 6848    init_test(cx, |_| {});
 6849
 6850    let mut cx = EditorTestContext::new(cx).await;
 6851
 6852    // Test caret-only selections
 6853    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6854    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6855        .unwrap();
 6856    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6857
 6858    // Test left-to-right selections
 6859    cx.set_state("abc\n«abcˇ»\nabc");
 6860    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6861        .unwrap();
 6862    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6863
 6864    // Test right-to-left selections
 6865    cx.set_state("abc\n«ˇabc»\nabc");
 6866    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6867        .unwrap();
 6868    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6869
 6870    // Test selecting whitespace with caret selection
 6871    cx.set_state("abc\nˇ   abc\nabc");
 6872    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6873        .unwrap();
 6874    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6875
 6876    // Test selecting whitespace with left-to-right selection
 6877    cx.set_state("abc\n«ˇ  »abc\nabc");
 6878    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6879        .unwrap();
 6880    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6881
 6882    // Test no matches with right-to-left selection
 6883    cx.set_state("abc\n«  ˇ»abc\nabc");
 6884    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6885        .unwrap();
 6886    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6887
 6888    // Test with a single word and clip_at_line_ends=true (#29823)
 6889    cx.set_state("aˇbc");
 6890    cx.update_editor(|e, window, cx| {
 6891        e.set_clip_at_line_ends(true, cx);
 6892        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6893        e.set_clip_at_line_ends(false, cx);
 6894    });
 6895    cx.assert_editor_state("«abcˇ»");
 6896}
 6897
 6898#[gpui::test]
 6899async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6900    init_test(cx, |_| {});
 6901
 6902    let mut cx = EditorTestContext::new(cx).await;
 6903
 6904    let large_body_1 = "\nd".repeat(200);
 6905    let large_body_2 = "\ne".repeat(200);
 6906
 6907    cx.set_state(&format!(
 6908        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6909    ));
 6910    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6911        let scroll_position = editor.scroll_position(cx);
 6912        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6913        scroll_position
 6914    });
 6915
 6916    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6917        .unwrap();
 6918    cx.assert_editor_state(&format!(
 6919        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6920    ));
 6921    let scroll_position_after_selection =
 6922        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6923    assert_eq!(
 6924        initial_scroll_position, scroll_position_after_selection,
 6925        "Scroll position should not change after selecting all matches"
 6926    );
 6927}
 6928
 6929#[gpui::test]
 6930async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6931    init_test(cx, |_| {});
 6932
 6933    let mut cx = EditorLspTestContext::new_rust(
 6934        lsp::ServerCapabilities {
 6935            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6936            ..Default::default()
 6937        },
 6938        cx,
 6939    )
 6940    .await;
 6941
 6942    cx.set_state(indoc! {"
 6943        line 1
 6944        line 2
 6945        linˇe 3
 6946        line 4
 6947        line 5
 6948    "});
 6949
 6950    // Make an edit
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.handle_input("X", window, cx);
 6953    });
 6954
 6955    // Move cursor to a different position
 6956    cx.update_editor(|editor, window, cx| {
 6957        editor.change_selections(None, window, cx, |s| {
 6958            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6959        });
 6960    });
 6961
 6962    cx.assert_editor_state(indoc! {"
 6963        line 1
 6964        line 2
 6965        linXe 3
 6966        line 4
 6967        liˇne 5
 6968    "});
 6969
 6970    cx.lsp
 6971        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6972            Ok(Some(vec![lsp::TextEdit::new(
 6973                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6974                "PREFIX ".to_string(),
 6975            )]))
 6976        });
 6977
 6978    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6979        .unwrap()
 6980        .await
 6981        .unwrap();
 6982
 6983    cx.assert_editor_state(indoc! {"
 6984        PREFIX line 1
 6985        line 2
 6986        linXe 3
 6987        line 4
 6988        liˇne 5
 6989    "});
 6990
 6991    // Undo formatting
 6992    cx.update_editor(|editor, window, cx| {
 6993        editor.undo(&Default::default(), window, cx);
 6994    });
 6995
 6996    // Verify cursor moved back to position after edit
 6997    cx.assert_editor_state(indoc! {"
 6998        line 1
 6999        line 2
 7000        linXˇe 3
 7001        line 4
 7002        line 5
 7003    "});
 7004}
 7005
 7006#[gpui::test]
 7007async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7008    init_test(cx, |_| {});
 7009
 7010    let mut cx = EditorTestContext::new(cx).await;
 7011
 7012    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7013    cx.update_editor(|editor, window, cx| {
 7014        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7015    });
 7016
 7017    cx.set_state(indoc! {"
 7018        line 1
 7019        line 2
 7020        linˇe 3
 7021        line 4
 7022        line 5
 7023        line 6
 7024        line 7
 7025        line 8
 7026        line 9
 7027        line 10
 7028    "});
 7029
 7030    let snapshot = cx.buffer_snapshot();
 7031    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7032
 7033    cx.update(|_, cx| {
 7034        provider.update(cx, |provider, _| {
 7035            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7036                id: None,
 7037                edits: vec![(edit_position..edit_position, "X".into())],
 7038                edit_preview: None,
 7039            }))
 7040        })
 7041    });
 7042
 7043    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7044    cx.update_editor(|editor, window, cx| {
 7045        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7046    });
 7047
 7048    cx.assert_editor_state(indoc! {"
 7049        line 1
 7050        line 2
 7051        lineXˇ 3
 7052        line 4
 7053        line 5
 7054        line 6
 7055        line 7
 7056        line 8
 7057        line 9
 7058        line 10
 7059    "});
 7060
 7061    cx.update_editor(|editor, window, cx| {
 7062        editor.change_selections(None, window, cx, |s| {
 7063            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7064        });
 7065    });
 7066
 7067    cx.assert_editor_state(indoc! {"
 7068        line 1
 7069        line 2
 7070        lineX 3
 7071        line 4
 7072        line 5
 7073        line 6
 7074        line 7
 7075        line 8
 7076        line 9
 7077        liˇne 10
 7078    "});
 7079
 7080    cx.update_editor(|editor, window, cx| {
 7081        editor.undo(&Default::default(), window, cx);
 7082    });
 7083
 7084    cx.assert_editor_state(indoc! {"
 7085        line 1
 7086        line 2
 7087        lineˇ 3
 7088        line 4
 7089        line 5
 7090        line 6
 7091        line 7
 7092        line 8
 7093        line 9
 7094        line 10
 7095    "});
 7096}
 7097
 7098#[gpui::test]
 7099async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7100    init_test(cx, |_| {});
 7101
 7102    let mut cx = EditorTestContext::new(cx).await;
 7103    cx.set_state(
 7104        r#"let foo = 2;
 7105lˇet foo = 2;
 7106let fooˇ = 2;
 7107let foo = 2;
 7108let foo = ˇ2;"#,
 7109    );
 7110
 7111    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7112        .unwrap();
 7113    cx.assert_editor_state(
 7114        r#"let foo = 2;
 7115«letˇ» foo = 2;
 7116let «fooˇ» = 2;
 7117let foo = 2;
 7118let foo = «2ˇ»;"#,
 7119    );
 7120
 7121    // noop for multiple selections with different contents
 7122    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7123        .unwrap();
 7124    cx.assert_editor_state(
 7125        r#"let foo = 2;
 7126«letˇ» foo = 2;
 7127let «fooˇ» = 2;
 7128let foo = 2;
 7129let foo = «2ˇ»;"#,
 7130    );
 7131
 7132    // Test last selection direction should be preserved
 7133    cx.set_state(
 7134        r#"let foo = 2;
 7135let foo = 2;
 7136let «fooˇ» = 2;
 7137let «ˇfoo» = 2;
 7138let foo = 2;"#,
 7139    );
 7140
 7141    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7142        .unwrap();
 7143    cx.assert_editor_state(
 7144        r#"let foo = 2;
 7145let foo = 2;
 7146let «fooˇ» = 2;
 7147let «ˇfoo» = 2;
 7148let «ˇfoo» = 2;"#,
 7149    );
 7150}
 7151
 7152#[gpui::test]
 7153async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7154    init_test(cx, |_| {});
 7155
 7156    let mut cx =
 7157        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7158
 7159    cx.assert_editor_state(indoc! {"
 7160        ˇbbb
 7161        ccc
 7162
 7163        bbb
 7164        ccc
 7165        "});
 7166    cx.dispatch_action(SelectPrevious::default());
 7167    cx.assert_editor_state(indoc! {"
 7168                «bbbˇ»
 7169                ccc
 7170
 7171                bbb
 7172                ccc
 7173                "});
 7174    cx.dispatch_action(SelectPrevious::default());
 7175    cx.assert_editor_state(indoc! {"
 7176                «bbbˇ»
 7177                ccc
 7178
 7179                «bbbˇ»
 7180                ccc
 7181                "});
 7182}
 7183
 7184#[gpui::test]
 7185async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7186    init_test(cx, |_| {});
 7187
 7188    let mut cx = EditorTestContext::new(cx).await;
 7189    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7190
 7191    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7192        .unwrap();
 7193    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7194
 7195    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7196        .unwrap();
 7197    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7198
 7199    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7200    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7201
 7202    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7203    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7204
 7205    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7206        .unwrap();
 7207    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7208
 7209    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7210        .unwrap();
 7211    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7212}
 7213
 7214#[gpui::test]
 7215async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7216    init_test(cx, |_| {});
 7217
 7218    let mut cx = EditorTestContext::new(cx).await;
 7219    cx.set_state("");
 7220
 7221    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7222        .unwrap();
 7223    cx.assert_editor_state("«aˇ»");
 7224    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7225        .unwrap();
 7226    cx.assert_editor_state("«aˇ»");
 7227}
 7228
 7229#[gpui::test]
 7230async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7231    init_test(cx, |_| {});
 7232
 7233    let mut cx = EditorTestContext::new(cx).await;
 7234    cx.set_state(
 7235        r#"let foo = 2;
 7236lˇet foo = 2;
 7237let fooˇ = 2;
 7238let foo = 2;
 7239let foo = ˇ2;"#,
 7240    );
 7241
 7242    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7243        .unwrap();
 7244    cx.assert_editor_state(
 7245        r#"let foo = 2;
 7246«letˇ» foo = 2;
 7247let «fooˇ» = 2;
 7248let foo = 2;
 7249let foo = «2ˇ»;"#,
 7250    );
 7251
 7252    // noop for multiple selections with different contents
 7253    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7254        .unwrap();
 7255    cx.assert_editor_state(
 7256        r#"let foo = 2;
 7257«letˇ» foo = 2;
 7258let «fooˇ» = 2;
 7259let foo = 2;
 7260let foo = «2ˇ»;"#,
 7261    );
 7262}
 7263
 7264#[gpui::test]
 7265async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7266    init_test(cx, |_| {});
 7267
 7268    let mut cx = EditorTestContext::new(cx).await;
 7269    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7270
 7271    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7272        .unwrap();
 7273    // selection direction is preserved
 7274    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7275
 7276    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7277        .unwrap();
 7278    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7279
 7280    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7281    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7282
 7283    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7284    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7285
 7286    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7287        .unwrap();
 7288    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7289
 7290    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7291        .unwrap();
 7292    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7293}
 7294
 7295#[gpui::test]
 7296async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7297    init_test(cx, |_| {});
 7298
 7299    let language = Arc::new(Language::new(
 7300        LanguageConfig::default(),
 7301        Some(tree_sitter_rust::LANGUAGE.into()),
 7302    ));
 7303
 7304    let text = r#"
 7305        use mod1::mod2::{mod3, mod4};
 7306
 7307        fn fn_1(param1: bool, param2: &str) {
 7308            let var1 = "text";
 7309        }
 7310    "#
 7311    .unindent();
 7312
 7313    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7314    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7315    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7316
 7317    editor
 7318        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7319        .await;
 7320
 7321    editor.update_in(cx, |editor, window, cx| {
 7322        editor.change_selections(None, window, cx, |s| {
 7323            s.select_display_ranges([
 7324                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7325                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7326                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7327            ]);
 7328        });
 7329        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7330    });
 7331    editor.update(cx, |editor, cx| {
 7332        assert_text_with_selections(
 7333            editor,
 7334            indoc! {r#"
 7335                use mod1::mod2::{mod3, «mod4ˇ»};
 7336
 7337                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7338                    let var1 = "«ˇtext»";
 7339                }
 7340            "#},
 7341            cx,
 7342        );
 7343    });
 7344
 7345    editor.update_in(cx, |editor, window, cx| {
 7346        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7347    });
 7348    editor.update(cx, |editor, cx| {
 7349        assert_text_with_selections(
 7350            editor,
 7351            indoc! {r#"
 7352                use mod1::mod2::«{mod3, mod4}ˇ»;
 7353
 7354                «ˇfn fn_1(param1: bool, param2: &str) {
 7355                    let var1 = "text";
 7356 7357            "#},
 7358            cx,
 7359        );
 7360    });
 7361
 7362    editor.update_in(cx, |editor, window, cx| {
 7363        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7364    });
 7365    assert_eq!(
 7366        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7367        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7368    );
 7369
 7370    // Trying to expand the selected syntax node one more time has no effect.
 7371    editor.update_in(cx, |editor, window, cx| {
 7372        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7373    });
 7374    assert_eq!(
 7375        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7376        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7377    );
 7378
 7379    editor.update_in(cx, |editor, window, cx| {
 7380        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7381    });
 7382    editor.update(cx, |editor, cx| {
 7383        assert_text_with_selections(
 7384            editor,
 7385            indoc! {r#"
 7386                use mod1::mod2::«{mod3, mod4}ˇ»;
 7387
 7388                «ˇfn fn_1(param1: bool, param2: &str) {
 7389                    let var1 = "text";
 7390 7391            "#},
 7392            cx,
 7393        );
 7394    });
 7395
 7396    editor.update_in(cx, |editor, window, cx| {
 7397        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7398    });
 7399    editor.update(cx, |editor, cx| {
 7400        assert_text_with_selections(
 7401            editor,
 7402            indoc! {r#"
 7403                use mod1::mod2::{mod3, «mod4ˇ»};
 7404
 7405                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7406                    let var1 = "«ˇtext»";
 7407                }
 7408            "#},
 7409            cx,
 7410        );
 7411    });
 7412
 7413    editor.update_in(cx, |editor, window, cx| {
 7414        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7415    });
 7416    editor.update(cx, |editor, cx| {
 7417        assert_text_with_selections(
 7418            editor,
 7419            indoc! {r#"
 7420                use mod1::mod2::{mod3, mo«ˇ»d4};
 7421
 7422                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7423                    let var1 = "te«ˇ»xt";
 7424                }
 7425            "#},
 7426            cx,
 7427        );
 7428    });
 7429
 7430    // Trying to shrink the selected syntax node one more time has no effect.
 7431    editor.update_in(cx, |editor, window, cx| {
 7432        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7433    });
 7434    editor.update_in(cx, |editor, _, cx| {
 7435        assert_text_with_selections(
 7436            editor,
 7437            indoc! {r#"
 7438                use mod1::mod2::{mod3, mo«ˇ»d4};
 7439
 7440                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7441                    let var1 = "te«ˇ»xt";
 7442                }
 7443            "#},
 7444            cx,
 7445        );
 7446    });
 7447
 7448    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7449    // a fold.
 7450    editor.update_in(cx, |editor, window, cx| {
 7451        editor.fold_creases(
 7452            vec![
 7453                Crease::simple(
 7454                    Point::new(0, 21)..Point::new(0, 24),
 7455                    FoldPlaceholder::test(),
 7456                ),
 7457                Crease::simple(
 7458                    Point::new(3, 20)..Point::new(3, 22),
 7459                    FoldPlaceholder::test(),
 7460                ),
 7461            ],
 7462            true,
 7463            window,
 7464            cx,
 7465        );
 7466        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7467    });
 7468    editor.update(cx, |editor, cx| {
 7469        assert_text_with_selections(
 7470            editor,
 7471            indoc! {r#"
 7472                use mod1::mod2::«{mod3, mod4}ˇ»;
 7473
 7474                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7475                    let var1 = "«ˇtext»";
 7476                }
 7477            "#},
 7478            cx,
 7479        );
 7480    });
 7481}
 7482
 7483#[gpui::test]
 7484async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7485    init_test(cx, |_| {});
 7486
 7487    let language = Arc::new(Language::new(
 7488        LanguageConfig::default(),
 7489        Some(tree_sitter_rust::LANGUAGE.into()),
 7490    ));
 7491
 7492    let text = "let a = 2;";
 7493
 7494    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7495    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7496    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7497
 7498    editor
 7499        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7500        .await;
 7501
 7502    // Test case 1: Cursor at end of word
 7503    editor.update_in(cx, |editor, window, cx| {
 7504        editor.change_selections(None, window, cx, |s| {
 7505            s.select_display_ranges([
 7506                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7507            ]);
 7508        });
 7509    });
 7510    editor.update(cx, |editor, cx| {
 7511        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7512    });
 7513    editor.update_in(cx, |editor, window, cx| {
 7514        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7515    });
 7516    editor.update(cx, |editor, cx| {
 7517        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7518    });
 7519    editor.update_in(cx, |editor, window, cx| {
 7520        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7521    });
 7522    editor.update(cx, |editor, cx| {
 7523        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7524    });
 7525
 7526    // Test case 2: Cursor at end of statement
 7527    editor.update_in(cx, |editor, window, cx| {
 7528        editor.change_selections(None, window, cx, |s| {
 7529            s.select_display_ranges([
 7530                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7531            ]);
 7532        });
 7533    });
 7534    editor.update(cx, |editor, cx| {
 7535        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7536    });
 7537    editor.update_in(cx, |editor, window, cx| {
 7538        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7539    });
 7540    editor.update(cx, |editor, cx| {
 7541        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7542    });
 7543}
 7544
 7545#[gpui::test]
 7546async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7547    init_test(cx, |_| {});
 7548
 7549    let language = Arc::new(Language::new(
 7550        LanguageConfig::default(),
 7551        Some(tree_sitter_rust::LANGUAGE.into()),
 7552    ));
 7553
 7554    let text = r#"
 7555        use mod1::mod2::{mod3, mod4};
 7556
 7557        fn fn_1(param1: bool, param2: &str) {
 7558            let var1 = "hello world";
 7559        }
 7560    "#
 7561    .unindent();
 7562
 7563    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7564    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7565    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7566
 7567    editor
 7568        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7569        .await;
 7570
 7571    // Test 1: Cursor on a letter of a string word
 7572    editor.update_in(cx, |editor, window, cx| {
 7573        editor.change_selections(None, window, cx, |s| {
 7574            s.select_display_ranges([
 7575                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7576            ]);
 7577        });
 7578    });
 7579    editor.update_in(cx, |editor, window, cx| {
 7580        assert_text_with_selections(
 7581            editor,
 7582            indoc! {r#"
 7583                use mod1::mod2::{mod3, mod4};
 7584
 7585                fn fn_1(param1: bool, param2: &str) {
 7586                    let var1 = "hˇello world";
 7587                }
 7588            "#},
 7589            cx,
 7590        );
 7591        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7592        assert_text_with_selections(
 7593            editor,
 7594            indoc! {r#"
 7595                use mod1::mod2::{mod3, mod4};
 7596
 7597                fn fn_1(param1: bool, param2: &str) {
 7598                    let var1 = "«ˇhello» world";
 7599                }
 7600            "#},
 7601            cx,
 7602        );
 7603    });
 7604
 7605    // Test 2: Partial selection within a word
 7606    editor.update_in(cx, |editor, window, cx| {
 7607        editor.change_selections(None, window, cx, |s| {
 7608            s.select_display_ranges([
 7609                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7610            ]);
 7611        });
 7612    });
 7613    editor.update_in(cx, |editor, window, cx| {
 7614        assert_text_with_selections(
 7615            editor,
 7616            indoc! {r#"
 7617                use mod1::mod2::{mod3, mod4};
 7618
 7619                fn fn_1(param1: bool, param2: &str) {
 7620                    let var1 = "h«elˇ»lo world";
 7621                }
 7622            "#},
 7623            cx,
 7624        );
 7625        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7626        assert_text_with_selections(
 7627            editor,
 7628            indoc! {r#"
 7629                use mod1::mod2::{mod3, mod4};
 7630
 7631                fn fn_1(param1: bool, param2: &str) {
 7632                    let var1 = "«ˇhello» world";
 7633                }
 7634            "#},
 7635            cx,
 7636        );
 7637    });
 7638
 7639    // Test 3: Complete word already selected
 7640    editor.update_in(cx, |editor, window, cx| {
 7641        editor.change_selections(None, window, cx, |s| {
 7642            s.select_display_ranges([
 7643                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7644            ]);
 7645        });
 7646    });
 7647    editor.update_in(cx, |editor, window, cx| {
 7648        assert_text_with_selections(
 7649            editor,
 7650            indoc! {r#"
 7651                use mod1::mod2::{mod3, mod4};
 7652
 7653                fn fn_1(param1: bool, param2: &str) {
 7654                    let var1 = "«helloˇ» world";
 7655                }
 7656            "#},
 7657            cx,
 7658        );
 7659        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7660        assert_text_with_selections(
 7661            editor,
 7662            indoc! {r#"
 7663                use mod1::mod2::{mod3, mod4};
 7664
 7665                fn fn_1(param1: bool, param2: &str) {
 7666                    let var1 = "«hello worldˇ»";
 7667                }
 7668            "#},
 7669            cx,
 7670        );
 7671    });
 7672
 7673    // Test 4: Selection spanning across words
 7674    editor.update_in(cx, |editor, window, cx| {
 7675        editor.change_selections(None, window, cx, |s| {
 7676            s.select_display_ranges([
 7677                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7678            ]);
 7679        });
 7680    });
 7681    editor.update_in(cx, |editor, window, cx| {
 7682        assert_text_with_selections(
 7683            editor,
 7684            indoc! {r#"
 7685                use mod1::mod2::{mod3, mod4};
 7686
 7687                fn fn_1(param1: bool, param2: &str) {
 7688                    let var1 = "hel«lo woˇ»rld";
 7689                }
 7690            "#},
 7691            cx,
 7692        );
 7693        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7694        assert_text_with_selections(
 7695            editor,
 7696            indoc! {r#"
 7697                use mod1::mod2::{mod3, mod4};
 7698
 7699                fn fn_1(param1: bool, param2: &str) {
 7700                    let var1 = "«ˇhello world»";
 7701                }
 7702            "#},
 7703            cx,
 7704        );
 7705    });
 7706
 7707    // Test 5: Expansion beyond string
 7708    editor.update_in(cx, |editor, window, cx| {
 7709        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7710        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7711        assert_text_with_selections(
 7712            editor,
 7713            indoc! {r#"
 7714                use mod1::mod2::{mod3, mod4};
 7715
 7716                fn fn_1(param1: bool, param2: &str) {
 7717                    «ˇlet var1 = "hello world";»
 7718                }
 7719            "#},
 7720            cx,
 7721        );
 7722    });
 7723}
 7724
 7725#[gpui::test]
 7726async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7727    init_test(cx, |_| {});
 7728
 7729    let base_text = r#"
 7730        impl A {
 7731            // this is an uncommitted comment
 7732
 7733            fn b() {
 7734                c();
 7735            }
 7736
 7737            // this is another uncommitted comment
 7738
 7739            fn d() {
 7740                // e
 7741                // f
 7742            }
 7743        }
 7744
 7745        fn g() {
 7746            // h
 7747        }
 7748    "#
 7749    .unindent();
 7750
 7751    let text = r#"
 7752        ˇimpl A {
 7753
 7754            fn b() {
 7755                c();
 7756            }
 7757
 7758            fn d() {
 7759                // e
 7760                // f
 7761            }
 7762        }
 7763
 7764        fn g() {
 7765            // h
 7766        }
 7767    "#
 7768    .unindent();
 7769
 7770    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7771    cx.set_state(&text);
 7772    cx.set_head_text(&base_text);
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7775    });
 7776
 7777    cx.assert_state_with_diff(
 7778        "
 7779        ˇimpl A {
 7780      -     // this is an uncommitted comment
 7781
 7782            fn b() {
 7783                c();
 7784            }
 7785
 7786      -     // this is another uncommitted comment
 7787      -
 7788            fn d() {
 7789                // e
 7790                // f
 7791            }
 7792        }
 7793
 7794        fn g() {
 7795            // h
 7796        }
 7797    "
 7798        .unindent(),
 7799    );
 7800
 7801    let expected_display_text = "
 7802        impl A {
 7803            // this is an uncommitted comment
 7804
 7805            fn b() {
 7806 7807            }
 7808
 7809            // this is another uncommitted comment
 7810
 7811            fn d() {
 7812 7813            }
 7814        }
 7815
 7816        fn g() {
 7817 7818        }
 7819        "
 7820    .unindent();
 7821
 7822    cx.update_editor(|editor, window, cx| {
 7823        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7824        assert_eq!(editor.display_text(cx), expected_display_text);
 7825    });
 7826}
 7827
 7828#[gpui::test]
 7829async fn test_autoindent(cx: &mut TestAppContext) {
 7830    init_test(cx, |_| {});
 7831
 7832    let language = Arc::new(
 7833        Language::new(
 7834            LanguageConfig {
 7835                brackets: BracketPairConfig {
 7836                    pairs: vec![
 7837                        BracketPair {
 7838                            start: "{".to_string(),
 7839                            end: "}".to_string(),
 7840                            close: false,
 7841                            surround: false,
 7842                            newline: true,
 7843                        },
 7844                        BracketPair {
 7845                            start: "(".to_string(),
 7846                            end: ")".to_string(),
 7847                            close: false,
 7848                            surround: false,
 7849                            newline: true,
 7850                        },
 7851                    ],
 7852                    ..Default::default()
 7853                },
 7854                ..Default::default()
 7855            },
 7856            Some(tree_sitter_rust::LANGUAGE.into()),
 7857        )
 7858        .with_indents_query(
 7859            r#"
 7860                (_ "(" ")" @end) @indent
 7861                (_ "{" "}" @end) @indent
 7862            "#,
 7863        )
 7864        .unwrap(),
 7865    );
 7866
 7867    let text = "fn a() {}";
 7868
 7869    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7870    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7871    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7872    editor
 7873        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7874        .await;
 7875
 7876    editor.update_in(cx, |editor, window, cx| {
 7877        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7878        editor.newline(&Newline, window, cx);
 7879        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7880        assert_eq!(
 7881            editor.selections.ranges(cx),
 7882            &[
 7883                Point::new(1, 4)..Point::new(1, 4),
 7884                Point::new(3, 4)..Point::new(3, 4),
 7885                Point::new(5, 0)..Point::new(5, 0)
 7886            ]
 7887        );
 7888    });
 7889}
 7890
 7891#[gpui::test]
 7892async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7893    init_test(cx, |_| {});
 7894
 7895    {
 7896        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7897        cx.set_state(indoc! {"
 7898            impl A {
 7899
 7900                fn b() {}
 7901
 7902            «fn c() {
 7903
 7904            }ˇ»
 7905            }
 7906        "});
 7907
 7908        cx.update_editor(|editor, window, cx| {
 7909            editor.autoindent(&Default::default(), window, cx);
 7910        });
 7911
 7912        cx.assert_editor_state(indoc! {"
 7913            impl A {
 7914
 7915                fn b() {}
 7916
 7917                «fn c() {
 7918
 7919                }ˇ»
 7920            }
 7921        "});
 7922    }
 7923
 7924    {
 7925        let mut cx = EditorTestContext::new_multibuffer(
 7926            cx,
 7927            [indoc! { "
 7928                impl A {
 7929                «
 7930                // a
 7931                fn b(){}
 7932                »
 7933                «
 7934                    }
 7935                    fn c(){}
 7936                »
 7937            "}],
 7938        );
 7939
 7940        let buffer = cx.update_editor(|editor, _, cx| {
 7941            let buffer = editor.buffer().update(cx, |buffer, _| {
 7942                buffer.all_buffers().iter().next().unwrap().clone()
 7943            });
 7944            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7945            buffer
 7946        });
 7947
 7948        cx.run_until_parked();
 7949        cx.update_editor(|editor, window, cx| {
 7950            editor.select_all(&Default::default(), window, cx);
 7951            editor.autoindent(&Default::default(), window, cx)
 7952        });
 7953        cx.run_until_parked();
 7954
 7955        cx.update(|_, cx| {
 7956            assert_eq!(
 7957                buffer.read(cx).text(),
 7958                indoc! { "
 7959                    impl A {
 7960
 7961                        // a
 7962                        fn b(){}
 7963
 7964
 7965                    }
 7966                    fn c(){}
 7967
 7968                " }
 7969            )
 7970        });
 7971    }
 7972}
 7973
 7974#[gpui::test]
 7975async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7976    init_test(cx, |_| {});
 7977
 7978    let mut cx = EditorTestContext::new(cx).await;
 7979
 7980    let language = Arc::new(Language::new(
 7981        LanguageConfig {
 7982            brackets: BracketPairConfig {
 7983                pairs: vec![
 7984                    BracketPair {
 7985                        start: "{".to_string(),
 7986                        end: "}".to_string(),
 7987                        close: true,
 7988                        surround: true,
 7989                        newline: true,
 7990                    },
 7991                    BracketPair {
 7992                        start: "(".to_string(),
 7993                        end: ")".to_string(),
 7994                        close: true,
 7995                        surround: true,
 7996                        newline: true,
 7997                    },
 7998                    BracketPair {
 7999                        start: "/*".to_string(),
 8000                        end: " */".to_string(),
 8001                        close: true,
 8002                        surround: true,
 8003                        newline: true,
 8004                    },
 8005                    BracketPair {
 8006                        start: "[".to_string(),
 8007                        end: "]".to_string(),
 8008                        close: false,
 8009                        surround: false,
 8010                        newline: true,
 8011                    },
 8012                    BracketPair {
 8013                        start: "\"".to_string(),
 8014                        end: "\"".to_string(),
 8015                        close: true,
 8016                        surround: true,
 8017                        newline: false,
 8018                    },
 8019                    BracketPair {
 8020                        start: "<".to_string(),
 8021                        end: ">".to_string(),
 8022                        close: false,
 8023                        surround: true,
 8024                        newline: true,
 8025                    },
 8026                ],
 8027                ..Default::default()
 8028            },
 8029            autoclose_before: "})]".to_string(),
 8030            ..Default::default()
 8031        },
 8032        Some(tree_sitter_rust::LANGUAGE.into()),
 8033    ));
 8034
 8035    cx.language_registry().add(language.clone());
 8036    cx.update_buffer(|buffer, cx| {
 8037        buffer.set_language(Some(language), cx);
 8038    });
 8039
 8040    cx.set_state(
 8041        &r#"
 8042            🏀ˇ
 8043            εˇ
 8044            ❤️ˇ
 8045        "#
 8046        .unindent(),
 8047    );
 8048
 8049    // autoclose multiple nested brackets at multiple cursors
 8050    cx.update_editor(|editor, window, cx| {
 8051        editor.handle_input("{", window, cx);
 8052        editor.handle_input("{", window, cx);
 8053        editor.handle_input("{", window, cx);
 8054    });
 8055    cx.assert_editor_state(
 8056        &"
 8057            🏀{{{ˇ}}}
 8058            ε{{{ˇ}}}
 8059            ❤️{{{ˇ}}}
 8060        "
 8061        .unindent(),
 8062    );
 8063
 8064    // insert a different closing bracket
 8065    cx.update_editor(|editor, window, cx| {
 8066        editor.handle_input(")", window, cx);
 8067    });
 8068    cx.assert_editor_state(
 8069        &"
 8070            🏀{{{)ˇ}}}
 8071            ε{{{)ˇ}}}
 8072            ❤️{{{)ˇ}}}
 8073        "
 8074        .unindent(),
 8075    );
 8076
 8077    // skip over the auto-closed brackets when typing a closing bracket
 8078    cx.update_editor(|editor, window, cx| {
 8079        editor.move_right(&MoveRight, window, cx);
 8080        editor.handle_input("}", window, cx);
 8081        editor.handle_input("}", window, cx);
 8082        editor.handle_input("}", window, cx);
 8083    });
 8084    cx.assert_editor_state(
 8085        &"
 8086            🏀{{{)}}}}ˇ
 8087            ε{{{)}}}}ˇ
 8088            ❤️{{{)}}}}ˇ
 8089        "
 8090        .unindent(),
 8091    );
 8092
 8093    // autoclose multi-character pairs
 8094    cx.set_state(
 8095        &"
 8096            ˇ
 8097            ˇ
 8098        "
 8099        .unindent(),
 8100    );
 8101    cx.update_editor(|editor, window, cx| {
 8102        editor.handle_input("/", window, cx);
 8103        editor.handle_input("*", window, cx);
 8104    });
 8105    cx.assert_editor_state(
 8106        &"
 8107            /*ˇ */
 8108            /*ˇ */
 8109        "
 8110        .unindent(),
 8111    );
 8112
 8113    // one cursor autocloses a multi-character pair, one cursor
 8114    // does not autoclose.
 8115    cx.set_state(
 8116        &"
 8117 8118            ˇ
 8119        "
 8120        .unindent(),
 8121    );
 8122    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8123    cx.assert_editor_state(
 8124        &"
 8125            /*ˇ */
 8126 8127        "
 8128        .unindent(),
 8129    );
 8130
 8131    // Don't autoclose if the next character isn't whitespace and isn't
 8132    // listed in the language's "autoclose_before" section.
 8133    cx.set_state("ˇa b");
 8134    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8135    cx.assert_editor_state("{ˇa b");
 8136
 8137    // Don't autoclose if `close` is false for the bracket pair
 8138    cx.set_state("ˇ");
 8139    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8140    cx.assert_editor_state("");
 8141
 8142    // Surround with brackets if text is selected
 8143    cx.set_state("«aˇ» b");
 8144    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8145    cx.assert_editor_state("{«aˇ»} b");
 8146
 8147    // Autoclose when not immediately after a word character
 8148    cx.set_state("a ˇ");
 8149    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8150    cx.assert_editor_state("a \"ˇ\"");
 8151
 8152    // Autoclose pair where the start and end characters are the same
 8153    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8154    cx.assert_editor_state("a \"\"ˇ");
 8155
 8156    // Don't autoclose when immediately after a word character
 8157    cx.set_state("");
 8158    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8159    cx.assert_editor_state("a\"ˇ");
 8160
 8161    // Do autoclose when after a non-word character
 8162    cx.set_state("");
 8163    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8164    cx.assert_editor_state("{\"ˇ\"");
 8165
 8166    // Non identical pairs autoclose regardless of preceding character
 8167    cx.set_state("");
 8168    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8169    cx.assert_editor_state("a{ˇ}");
 8170
 8171    // Don't autoclose pair if autoclose is disabled
 8172    cx.set_state("ˇ");
 8173    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8174    cx.assert_editor_state("");
 8175
 8176    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8177    cx.set_state("«aˇ» b");
 8178    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8179    cx.assert_editor_state("<«aˇ»> b");
 8180}
 8181
 8182#[gpui::test]
 8183async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8184    init_test(cx, |settings| {
 8185        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8186    });
 8187
 8188    let mut cx = EditorTestContext::new(cx).await;
 8189
 8190    let language = Arc::new(Language::new(
 8191        LanguageConfig {
 8192            brackets: BracketPairConfig {
 8193                pairs: vec![
 8194                    BracketPair {
 8195                        start: "{".to_string(),
 8196                        end: "}".to_string(),
 8197                        close: true,
 8198                        surround: true,
 8199                        newline: true,
 8200                    },
 8201                    BracketPair {
 8202                        start: "(".to_string(),
 8203                        end: ")".to_string(),
 8204                        close: true,
 8205                        surround: true,
 8206                        newline: true,
 8207                    },
 8208                    BracketPair {
 8209                        start: "[".to_string(),
 8210                        end: "]".to_string(),
 8211                        close: false,
 8212                        surround: false,
 8213                        newline: true,
 8214                    },
 8215                ],
 8216                ..Default::default()
 8217            },
 8218            autoclose_before: "})]".to_string(),
 8219            ..Default::default()
 8220        },
 8221        Some(tree_sitter_rust::LANGUAGE.into()),
 8222    ));
 8223
 8224    cx.language_registry().add(language.clone());
 8225    cx.update_buffer(|buffer, cx| {
 8226        buffer.set_language(Some(language), cx);
 8227    });
 8228
 8229    cx.set_state(
 8230        &"
 8231            ˇ
 8232            ˇ
 8233            ˇ
 8234        "
 8235        .unindent(),
 8236    );
 8237
 8238    // ensure only matching closing brackets are skipped over
 8239    cx.update_editor(|editor, window, cx| {
 8240        editor.handle_input("}", window, cx);
 8241        editor.move_left(&MoveLeft, window, cx);
 8242        editor.handle_input(")", window, cx);
 8243        editor.move_left(&MoveLeft, window, cx);
 8244    });
 8245    cx.assert_editor_state(
 8246        &"
 8247            ˇ)}
 8248            ˇ)}
 8249            ˇ)}
 8250        "
 8251        .unindent(),
 8252    );
 8253
 8254    // skip-over closing brackets at multiple cursors
 8255    cx.update_editor(|editor, window, cx| {
 8256        editor.handle_input(")", window, cx);
 8257        editor.handle_input("}", window, cx);
 8258    });
 8259    cx.assert_editor_state(
 8260        &"
 8261            )}ˇ
 8262            )}ˇ
 8263            )}ˇ
 8264        "
 8265        .unindent(),
 8266    );
 8267
 8268    // ignore non-close brackets
 8269    cx.update_editor(|editor, window, cx| {
 8270        editor.handle_input("]", window, cx);
 8271        editor.move_left(&MoveLeft, window, cx);
 8272        editor.handle_input("]", window, cx);
 8273    });
 8274    cx.assert_editor_state(
 8275        &"
 8276            )}]ˇ]
 8277            )}]ˇ]
 8278            )}]ˇ]
 8279        "
 8280        .unindent(),
 8281    );
 8282}
 8283
 8284#[gpui::test]
 8285async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8286    init_test(cx, |_| {});
 8287
 8288    let mut cx = EditorTestContext::new(cx).await;
 8289
 8290    let html_language = Arc::new(
 8291        Language::new(
 8292            LanguageConfig {
 8293                name: "HTML".into(),
 8294                brackets: BracketPairConfig {
 8295                    pairs: vec![
 8296                        BracketPair {
 8297                            start: "<".into(),
 8298                            end: ">".into(),
 8299                            close: true,
 8300                            ..Default::default()
 8301                        },
 8302                        BracketPair {
 8303                            start: "{".into(),
 8304                            end: "}".into(),
 8305                            close: true,
 8306                            ..Default::default()
 8307                        },
 8308                        BracketPair {
 8309                            start: "(".into(),
 8310                            end: ")".into(),
 8311                            close: true,
 8312                            ..Default::default()
 8313                        },
 8314                    ],
 8315                    ..Default::default()
 8316                },
 8317                autoclose_before: "})]>".into(),
 8318                ..Default::default()
 8319            },
 8320            Some(tree_sitter_html::LANGUAGE.into()),
 8321        )
 8322        .with_injection_query(
 8323            r#"
 8324            (script_element
 8325                (raw_text) @injection.content
 8326                (#set! injection.language "javascript"))
 8327            "#,
 8328        )
 8329        .unwrap(),
 8330    );
 8331
 8332    let javascript_language = Arc::new(Language::new(
 8333        LanguageConfig {
 8334            name: "JavaScript".into(),
 8335            brackets: BracketPairConfig {
 8336                pairs: vec![
 8337                    BracketPair {
 8338                        start: "/*".into(),
 8339                        end: " */".into(),
 8340                        close: true,
 8341                        ..Default::default()
 8342                    },
 8343                    BracketPair {
 8344                        start: "{".into(),
 8345                        end: "}".into(),
 8346                        close: true,
 8347                        ..Default::default()
 8348                    },
 8349                    BracketPair {
 8350                        start: "(".into(),
 8351                        end: ")".into(),
 8352                        close: true,
 8353                        ..Default::default()
 8354                    },
 8355                ],
 8356                ..Default::default()
 8357            },
 8358            autoclose_before: "})]>".into(),
 8359            ..Default::default()
 8360        },
 8361        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8362    ));
 8363
 8364    cx.language_registry().add(html_language.clone());
 8365    cx.language_registry().add(javascript_language.clone());
 8366
 8367    cx.update_buffer(|buffer, cx| {
 8368        buffer.set_language(Some(html_language), cx);
 8369    });
 8370
 8371    cx.set_state(
 8372        &r#"
 8373            <body>ˇ
 8374                <script>
 8375                    var x = 1;ˇ
 8376                </script>
 8377            </body>ˇ
 8378        "#
 8379        .unindent(),
 8380    );
 8381
 8382    // Precondition: different languages are active at different locations.
 8383    cx.update_editor(|editor, window, cx| {
 8384        let snapshot = editor.snapshot(window, cx);
 8385        let cursors = editor.selections.ranges::<usize>(cx);
 8386        let languages = cursors
 8387            .iter()
 8388            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8389            .collect::<Vec<_>>();
 8390        assert_eq!(
 8391            languages,
 8392            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8393        );
 8394    });
 8395
 8396    // Angle brackets autoclose in HTML, but not JavaScript.
 8397    cx.update_editor(|editor, window, cx| {
 8398        editor.handle_input("<", window, cx);
 8399        editor.handle_input("a", window, cx);
 8400    });
 8401    cx.assert_editor_state(
 8402        &r#"
 8403            <body><aˇ>
 8404                <script>
 8405                    var x = 1;<aˇ
 8406                </script>
 8407            </body><aˇ>
 8408        "#
 8409        .unindent(),
 8410    );
 8411
 8412    // Curly braces and parens autoclose in both HTML and JavaScript.
 8413    cx.update_editor(|editor, window, cx| {
 8414        editor.handle_input(" b=", window, cx);
 8415        editor.handle_input("{", window, cx);
 8416        editor.handle_input("c", window, cx);
 8417        editor.handle_input("(", window, cx);
 8418    });
 8419    cx.assert_editor_state(
 8420        &r#"
 8421            <body><a b={c(ˇ)}>
 8422                <script>
 8423                    var x = 1;<a b={c(ˇ)}
 8424                </script>
 8425            </body><a b={c(ˇ)}>
 8426        "#
 8427        .unindent(),
 8428    );
 8429
 8430    // Brackets that were already autoclosed are skipped.
 8431    cx.update_editor(|editor, window, cx| {
 8432        editor.handle_input(")", window, cx);
 8433        editor.handle_input("d", window, cx);
 8434        editor.handle_input("}", window, cx);
 8435    });
 8436    cx.assert_editor_state(
 8437        &r#"
 8438            <body><a b={c()d}ˇ>
 8439                <script>
 8440                    var x = 1;<a b={c()d}ˇ
 8441                </script>
 8442            </body><a b={c()d}ˇ>
 8443        "#
 8444        .unindent(),
 8445    );
 8446    cx.update_editor(|editor, window, cx| {
 8447        editor.handle_input(">", window, cx);
 8448    });
 8449    cx.assert_editor_state(
 8450        &r#"
 8451            <body><a b={c()d}>ˇ
 8452                <script>
 8453                    var x = 1;<a b={c()d}>ˇ
 8454                </script>
 8455            </body><a b={c()d}>ˇ
 8456        "#
 8457        .unindent(),
 8458    );
 8459
 8460    // Reset
 8461    cx.set_state(
 8462        &r#"
 8463            <body>ˇ
 8464                <script>
 8465                    var x = 1;ˇ
 8466                </script>
 8467            </body>ˇ
 8468        "#
 8469        .unindent(),
 8470    );
 8471
 8472    cx.update_editor(|editor, window, cx| {
 8473        editor.handle_input("<", window, cx);
 8474    });
 8475    cx.assert_editor_state(
 8476        &r#"
 8477            <body><ˇ>
 8478                <script>
 8479                    var x = 1;<ˇ
 8480                </script>
 8481            </body><ˇ>
 8482        "#
 8483        .unindent(),
 8484    );
 8485
 8486    // When backspacing, the closing angle brackets are removed.
 8487    cx.update_editor(|editor, window, cx| {
 8488        editor.backspace(&Backspace, window, cx);
 8489    });
 8490    cx.assert_editor_state(
 8491        &r#"
 8492            <body>ˇ
 8493                <script>
 8494                    var x = 1;ˇ
 8495                </script>
 8496            </body>ˇ
 8497        "#
 8498        .unindent(),
 8499    );
 8500
 8501    // Block comments autoclose in JavaScript, but not HTML.
 8502    cx.update_editor(|editor, window, cx| {
 8503        editor.handle_input("/", window, cx);
 8504        editor.handle_input("*", window, cx);
 8505    });
 8506    cx.assert_editor_state(
 8507        &r#"
 8508            <body>/*ˇ
 8509                <script>
 8510                    var x = 1;/*ˇ */
 8511                </script>
 8512            </body>/*ˇ
 8513        "#
 8514        .unindent(),
 8515    );
 8516}
 8517
 8518#[gpui::test]
 8519async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8520    init_test(cx, |_| {});
 8521
 8522    let mut cx = EditorTestContext::new(cx).await;
 8523
 8524    let rust_language = Arc::new(
 8525        Language::new(
 8526            LanguageConfig {
 8527                name: "Rust".into(),
 8528                brackets: serde_json::from_value(json!([
 8529                    { "start": "{", "end": "}", "close": true, "newline": true },
 8530                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8531                ]))
 8532                .unwrap(),
 8533                autoclose_before: "})]>".into(),
 8534                ..Default::default()
 8535            },
 8536            Some(tree_sitter_rust::LANGUAGE.into()),
 8537        )
 8538        .with_override_query("(string_literal) @string")
 8539        .unwrap(),
 8540    );
 8541
 8542    cx.language_registry().add(rust_language.clone());
 8543    cx.update_buffer(|buffer, cx| {
 8544        buffer.set_language(Some(rust_language), cx);
 8545    });
 8546
 8547    cx.set_state(
 8548        &r#"
 8549            let x = ˇ
 8550        "#
 8551        .unindent(),
 8552    );
 8553
 8554    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8555    cx.update_editor(|editor, window, cx| {
 8556        editor.handle_input("\"", window, cx);
 8557    });
 8558    cx.assert_editor_state(
 8559        &r#"
 8560            let x = "ˇ"
 8561        "#
 8562        .unindent(),
 8563    );
 8564
 8565    // Inserting another quotation mark. The cursor moves across the existing
 8566    // automatically-inserted quotation mark.
 8567    cx.update_editor(|editor, window, cx| {
 8568        editor.handle_input("\"", window, cx);
 8569    });
 8570    cx.assert_editor_state(
 8571        &r#"
 8572            let x = ""ˇ
 8573        "#
 8574        .unindent(),
 8575    );
 8576
 8577    // Reset
 8578    cx.set_state(
 8579        &r#"
 8580            let x = ˇ
 8581        "#
 8582        .unindent(),
 8583    );
 8584
 8585    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8586    cx.update_editor(|editor, window, cx| {
 8587        editor.handle_input("\"", window, cx);
 8588        editor.handle_input(" ", window, cx);
 8589        editor.move_left(&Default::default(), window, cx);
 8590        editor.handle_input("\\", window, cx);
 8591        editor.handle_input("\"", window, cx);
 8592    });
 8593    cx.assert_editor_state(
 8594        &r#"
 8595            let x = "\"ˇ "
 8596        "#
 8597        .unindent(),
 8598    );
 8599
 8600    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8601    // mark. Nothing is inserted.
 8602    cx.update_editor(|editor, window, cx| {
 8603        editor.move_right(&Default::default(), window, cx);
 8604        editor.handle_input("\"", window, cx);
 8605    });
 8606    cx.assert_editor_state(
 8607        &r#"
 8608            let x = "\" "ˇ
 8609        "#
 8610        .unindent(),
 8611    );
 8612}
 8613
 8614#[gpui::test]
 8615async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8616    init_test(cx, |_| {});
 8617
 8618    let language = Arc::new(Language::new(
 8619        LanguageConfig {
 8620            brackets: BracketPairConfig {
 8621                pairs: vec![
 8622                    BracketPair {
 8623                        start: "{".to_string(),
 8624                        end: "}".to_string(),
 8625                        close: true,
 8626                        surround: true,
 8627                        newline: true,
 8628                    },
 8629                    BracketPair {
 8630                        start: "/* ".to_string(),
 8631                        end: "*/".to_string(),
 8632                        close: true,
 8633                        surround: true,
 8634                        ..Default::default()
 8635                    },
 8636                ],
 8637                ..Default::default()
 8638            },
 8639            ..Default::default()
 8640        },
 8641        Some(tree_sitter_rust::LANGUAGE.into()),
 8642    ));
 8643
 8644    let text = r#"
 8645        a
 8646        b
 8647        c
 8648    "#
 8649    .unindent();
 8650
 8651    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8652    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8653    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8654    editor
 8655        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8656        .await;
 8657
 8658    editor.update_in(cx, |editor, window, cx| {
 8659        editor.change_selections(None, window, cx, |s| {
 8660            s.select_display_ranges([
 8661                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8662                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8663                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8664            ])
 8665        });
 8666
 8667        editor.handle_input("{", window, cx);
 8668        editor.handle_input("{", window, cx);
 8669        editor.handle_input("{", window, cx);
 8670        assert_eq!(
 8671            editor.text(cx),
 8672            "
 8673                {{{a}}}
 8674                {{{b}}}
 8675                {{{c}}}
 8676            "
 8677            .unindent()
 8678        );
 8679        assert_eq!(
 8680            editor.selections.display_ranges(cx),
 8681            [
 8682                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8683                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8684                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8685            ]
 8686        );
 8687
 8688        editor.undo(&Undo, window, cx);
 8689        editor.undo(&Undo, window, cx);
 8690        editor.undo(&Undo, window, cx);
 8691        assert_eq!(
 8692            editor.text(cx),
 8693            "
 8694                a
 8695                b
 8696                c
 8697            "
 8698            .unindent()
 8699        );
 8700        assert_eq!(
 8701            editor.selections.display_ranges(cx),
 8702            [
 8703                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8704                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8705                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8706            ]
 8707        );
 8708
 8709        // Ensure inserting the first character of a multi-byte bracket pair
 8710        // doesn't surround the selections with the bracket.
 8711        editor.handle_input("/", window, cx);
 8712        assert_eq!(
 8713            editor.text(cx),
 8714            "
 8715                /
 8716                /
 8717                /
 8718            "
 8719            .unindent()
 8720        );
 8721        assert_eq!(
 8722            editor.selections.display_ranges(cx),
 8723            [
 8724                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8725                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8726                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8727            ]
 8728        );
 8729
 8730        editor.undo(&Undo, window, cx);
 8731        assert_eq!(
 8732            editor.text(cx),
 8733            "
 8734                a
 8735                b
 8736                c
 8737            "
 8738            .unindent()
 8739        );
 8740        assert_eq!(
 8741            editor.selections.display_ranges(cx),
 8742            [
 8743                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8744                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8745                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8746            ]
 8747        );
 8748
 8749        // Ensure inserting the last character of a multi-byte bracket pair
 8750        // doesn't surround the selections with the bracket.
 8751        editor.handle_input("*", window, cx);
 8752        assert_eq!(
 8753            editor.text(cx),
 8754            "
 8755                *
 8756                *
 8757                *
 8758            "
 8759            .unindent()
 8760        );
 8761        assert_eq!(
 8762            editor.selections.display_ranges(cx),
 8763            [
 8764                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8765                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8766                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8767            ]
 8768        );
 8769    });
 8770}
 8771
 8772#[gpui::test]
 8773async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8774    init_test(cx, |_| {});
 8775
 8776    let language = Arc::new(Language::new(
 8777        LanguageConfig {
 8778            brackets: BracketPairConfig {
 8779                pairs: vec![BracketPair {
 8780                    start: "{".to_string(),
 8781                    end: "}".to_string(),
 8782                    close: true,
 8783                    surround: true,
 8784                    newline: true,
 8785                }],
 8786                ..Default::default()
 8787            },
 8788            autoclose_before: "}".to_string(),
 8789            ..Default::default()
 8790        },
 8791        Some(tree_sitter_rust::LANGUAGE.into()),
 8792    ));
 8793
 8794    let text = r#"
 8795        a
 8796        b
 8797        c
 8798    "#
 8799    .unindent();
 8800
 8801    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8802    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8803    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8804    editor
 8805        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8806        .await;
 8807
 8808    editor.update_in(cx, |editor, window, cx| {
 8809        editor.change_selections(None, window, cx, |s| {
 8810            s.select_ranges([
 8811                Point::new(0, 1)..Point::new(0, 1),
 8812                Point::new(1, 1)..Point::new(1, 1),
 8813                Point::new(2, 1)..Point::new(2, 1),
 8814            ])
 8815        });
 8816
 8817        editor.handle_input("{", window, cx);
 8818        editor.handle_input("{", window, cx);
 8819        editor.handle_input("_", window, cx);
 8820        assert_eq!(
 8821            editor.text(cx),
 8822            "
 8823                a{{_}}
 8824                b{{_}}
 8825                c{{_}}
 8826            "
 8827            .unindent()
 8828        );
 8829        assert_eq!(
 8830            editor.selections.ranges::<Point>(cx),
 8831            [
 8832                Point::new(0, 4)..Point::new(0, 4),
 8833                Point::new(1, 4)..Point::new(1, 4),
 8834                Point::new(2, 4)..Point::new(2, 4)
 8835            ]
 8836        );
 8837
 8838        editor.backspace(&Default::default(), window, cx);
 8839        editor.backspace(&Default::default(), window, cx);
 8840        assert_eq!(
 8841            editor.text(cx),
 8842            "
 8843                a{}
 8844                b{}
 8845                c{}
 8846            "
 8847            .unindent()
 8848        );
 8849        assert_eq!(
 8850            editor.selections.ranges::<Point>(cx),
 8851            [
 8852                Point::new(0, 2)..Point::new(0, 2),
 8853                Point::new(1, 2)..Point::new(1, 2),
 8854                Point::new(2, 2)..Point::new(2, 2)
 8855            ]
 8856        );
 8857
 8858        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8859        assert_eq!(
 8860            editor.text(cx),
 8861            "
 8862                a
 8863                b
 8864                c
 8865            "
 8866            .unindent()
 8867        );
 8868        assert_eq!(
 8869            editor.selections.ranges::<Point>(cx),
 8870            [
 8871                Point::new(0, 1)..Point::new(0, 1),
 8872                Point::new(1, 1)..Point::new(1, 1),
 8873                Point::new(2, 1)..Point::new(2, 1)
 8874            ]
 8875        );
 8876    });
 8877}
 8878
 8879#[gpui::test]
 8880async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8881    init_test(cx, |settings| {
 8882        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8883    });
 8884
 8885    let mut cx = EditorTestContext::new(cx).await;
 8886
 8887    let language = Arc::new(Language::new(
 8888        LanguageConfig {
 8889            brackets: BracketPairConfig {
 8890                pairs: vec![
 8891                    BracketPair {
 8892                        start: "{".to_string(),
 8893                        end: "}".to_string(),
 8894                        close: true,
 8895                        surround: true,
 8896                        newline: true,
 8897                    },
 8898                    BracketPair {
 8899                        start: "(".to_string(),
 8900                        end: ")".to_string(),
 8901                        close: true,
 8902                        surround: true,
 8903                        newline: true,
 8904                    },
 8905                    BracketPair {
 8906                        start: "[".to_string(),
 8907                        end: "]".to_string(),
 8908                        close: false,
 8909                        surround: true,
 8910                        newline: true,
 8911                    },
 8912                ],
 8913                ..Default::default()
 8914            },
 8915            autoclose_before: "})]".to_string(),
 8916            ..Default::default()
 8917        },
 8918        Some(tree_sitter_rust::LANGUAGE.into()),
 8919    ));
 8920
 8921    cx.language_registry().add(language.clone());
 8922    cx.update_buffer(|buffer, cx| {
 8923        buffer.set_language(Some(language), cx);
 8924    });
 8925
 8926    cx.set_state(
 8927        &"
 8928            {(ˇ)}
 8929            [[ˇ]]
 8930            {(ˇ)}
 8931        "
 8932        .unindent(),
 8933    );
 8934
 8935    cx.update_editor(|editor, window, cx| {
 8936        editor.backspace(&Default::default(), window, cx);
 8937        editor.backspace(&Default::default(), window, cx);
 8938    });
 8939
 8940    cx.assert_editor_state(
 8941        &"
 8942            ˇ
 8943            ˇ]]
 8944            ˇ
 8945        "
 8946        .unindent(),
 8947    );
 8948
 8949    cx.update_editor(|editor, window, cx| {
 8950        editor.handle_input("{", window, cx);
 8951        editor.handle_input("{", window, cx);
 8952        editor.move_right(&MoveRight, window, cx);
 8953        editor.move_right(&MoveRight, window, cx);
 8954        editor.move_left(&MoveLeft, window, cx);
 8955        editor.move_left(&MoveLeft, window, cx);
 8956        editor.backspace(&Default::default(), window, cx);
 8957    });
 8958
 8959    cx.assert_editor_state(
 8960        &"
 8961            {ˇ}
 8962            {ˇ}]]
 8963            {ˇ}
 8964        "
 8965        .unindent(),
 8966    );
 8967
 8968    cx.update_editor(|editor, window, cx| {
 8969        editor.backspace(&Default::default(), window, cx);
 8970    });
 8971
 8972    cx.assert_editor_state(
 8973        &"
 8974            ˇ
 8975            ˇ]]
 8976            ˇ
 8977        "
 8978        .unindent(),
 8979    );
 8980}
 8981
 8982#[gpui::test]
 8983async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8984    init_test(cx, |_| {});
 8985
 8986    let language = Arc::new(Language::new(
 8987        LanguageConfig::default(),
 8988        Some(tree_sitter_rust::LANGUAGE.into()),
 8989    ));
 8990
 8991    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8992    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8993    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8994    editor
 8995        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8996        .await;
 8997
 8998    editor.update_in(cx, |editor, window, cx| {
 8999        editor.set_auto_replace_emoji_shortcode(true);
 9000
 9001        editor.handle_input("Hello ", window, cx);
 9002        editor.handle_input(":wave", window, cx);
 9003        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9004
 9005        editor.handle_input(":", window, cx);
 9006        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9007
 9008        editor.handle_input(" :smile", window, cx);
 9009        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9010
 9011        editor.handle_input(":", window, cx);
 9012        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9013
 9014        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9015        editor.handle_input(":wave", window, cx);
 9016        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9017
 9018        editor.handle_input(":", window, cx);
 9019        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9020
 9021        editor.handle_input(":1", window, cx);
 9022        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9023
 9024        editor.handle_input(":", window, cx);
 9025        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9026
 9027        // Ensure shortcode does not get replaced when it is part of a word
 9028        editor.handle_input(" Test:wave", window, cx);
 9029        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9030
 9031        editor.handle_input(":", window, cx);
 9032        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9033
 9034        editor.set_auto_replace_emoji_shortcode(false);
 9035
 9036        // Ensure shortcode does not get replaced when auto replace is off
 9037        editor.handle_input(" :wave", window, cx);
 9038        assert_eq!(
 9039            editor.text(cx),
 9040            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9041        );
 9042
 9043        editor.handle_input(":", window, cx);
 9044        assert_eq!(
 9045            editor.text(cx),
 9046            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9047        );
 9048    });
 9049}
 9050
 9051#[gpui::test]
 9052async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9053    init_test(cx, |_| {});
 9054
 9055    let (text, insertion_ranges) = marked_text_ranges(
 9056        indoc! {"
 9057            ˇ
 9058        "},
 9059        false,
 9060    );
 9061
 9062    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9063    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9064
 9065    _ = editor.update_in(cx, |editor, window, cx| {
 9066        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9067
 9068        editor
 9069            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9070            .unwrap();
 9071
 9072        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9073            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9074            assert_eq!(editor.text(cx), expected_text);
 9075            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9076        }
 9077
 9078        assert(
 9079            editor,
 9080            cx,
 9081            indoc! {"
 9082            type «» =•
 9083            "},
 9084        );
 9085
 9086        assert!(editor.context_menu_visible(), "There should be a matches");
 9087    });
 9088}
 9089
 9090#[gpui::test]
 9091async fn test_snippets(cx: &mut TestAppContext) {
 9092    init_test(cx, |_| {});
 9093
 9094    let mut cx = EditorTestContext::new(cx).await;
 9095
 9096    cx.set_state(indoc! {"
 9097        a.ˇ b
 9098        a.ˇ b
 9099        a.ˇ b
 9100    "});
 9101
 9102    cx.update_editor(|editor, window, cx| {
 9103        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9104        let insertion_ranges = editor
 9105            .selections
 9106            .all(cx)
 9107            .iter()
 9108            .map(|s| s.range().clone())
 9109            .collect::<Vec<_>>();
 9110        editor
 9111            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9112            .unwrap();
 9113    });
 9114
 9115    cx.assert_editor_state(indoc! {"
 9116        a.f(«oneˇ», two, «threeˇ») b
 9117        a.f(«oneˇ», two, «threeˇ») b
 9118        a.f(«oneˇ», two, «threeˇ») b
 9119    "});
 9120
 9121    // Can't move earlier than the first tab stop
 9122    cx.update_editor(|editor, window, cx| {
 9123        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9124    });
 9125    cx.assert_editor_state(indoc! {"
 9126        a.f(«oneˇ», two, «threeˇ») b
 9127        a.f(«oneˇ», two, «threeˇ») b
 9128        a.f(«oneˇ», two, «threeˇ») b
 9129    "});
 9130
 9131    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9132    cx.assert_editor_state(indoc! {"
 9133        a.f(one, «twoˇ», three) b
 9134        a.f(one, «twoˇ», three) b
 9135        a.f(one, «twoˇ», three) b
 9136    "});
 9137
 9138    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9139    cx.assert_editor_state(indoc! {"
 9140        a.f(«oneˇ», two, «threeˇ») b
 9141        a.f(«oneˇ», two, «threeˇ») b
 9142        a.f(«oneˇ», two, «threeˇ») b
 9143    "});
 9144
 9145    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9146    cx.assert_editor_state(indoc! {"
 9147        a.f(one, «twoˇ», three) b
 9148        a.f(one, «twoˇ», three) b
 9149        a.f(one, «twoˇ», three) b
 9150    "});
 9151    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9152    cx.assert_editor_state(indoc! {"
 9153        a.f(one, two, three)ˇ b
 9154        a.f(one, two, three)ˇ b
 9155        a.f(one, two, three)ˇ b
 9156    "});
 9157
 9158    // As soon as the last tab stop is reached, snippet state is gone
 9159    cx.update_editor(|editor, window, cx| {
 9160        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9161    });
 9162    cx.assert_editor_state(indoc! {"
 9163        a.f(one, two, three)ˇ b
 9164        a.f(one, two, three)ˇ b
 9165        a.f(one, two, three)ˇ b
 9166    "});
 9167}
 9168
 9169#[gpui::test]
 9170async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9171    init_test(cx, |_| {});
 9172
 9173    let mut cx = EditorTestContext::new(cx).await;
 9174
 9175    cx.update_editor(|editor, window, cx| {
 9176        let snippet = Snippet::parse(indoc! {"
 9177            /*
 9178             * Multiline comment with leading indentation
 9179             *
 9180             * $1
 9181             */
 9182            $0"})
 9183        .unwrap();
 9184        let insertion_ranges = editor
 9185            .selections
 9186            .all(cx)
 9187            .iter()
 9188            .map(|s| s.range().clone())
 9189            .collect::<Vec<_>>();
 9190        editor
 9191            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9192            .unwrap();
 9193    });
 9194
 9195    cx.assert_editor_state(indoc! {"
 9196        /*
 9197         * Multiline comment with leading indentation
 9198         *
 9199         * ˇ
 9200         */
 9201    "});
 9202
 9203    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9204    cx.assert_editor_state(indoc! {"
 9205        /*
 9206         * Multiline comment with leading indentation
 9207         *
 9208         *•
 9209         */
 9210        ˇ"});
 9211}
 9212
 9213#[gpui::test]
 9214async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9215    init_test(cx, |_| {});
 9216
 9217    let fs = FakeFs::new(cx.executor());
 9218    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9219
 9220    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9221
 9222    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9223    language_registry.add(rust_lang());
 9224    let mut fake_servers = language_registry.register_fake_lsp(
 9225        "Rust",
 9226        FakeLspAdapter {
 9227            capabilities: lsp::ServerCapabilities {
 9228                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9229                ..Default::default()
 9230            },
 9231            ..Default::default()
 9232        },
 9233    );
 9234
 9235    let buffer = project
 9236        .update(cx, |project, cx| {
 9237            project.open_local_buffer(path!("/file.rs"), cx)
 9238        })
 9239        .await
 9240        .unwrap();
 9241
 9242    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9243    let (editor, cx) = cx.add_window_view(|window, cx| {
 9244        build_editor_with_project(project.clone(), buffer, window, cx)
 9245    });
 9246    editor.update_in(cx, |editor, window, cx| {
 9247        editor.set_text("one\ntwo\nthree\n", window, cx)
 9248    });
 9249    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9250
 9251    cx.executor().start_waiting();
 9252    let fake_server = fake_servers.next().await.unwrap();
 9253
 9254    {
 9255        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9256            move |params, _| async move {
 9257                assert_eq!(
 9258                    params.text_document.uri,
 9259                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9260                );
 9261                assert_eq!(params.options.tab_size, 4);
 9262                Ok(Some(vec![lsp::TextEdit::new(
 9263                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9264                    ", ".to_string(),
 9265                )]))
 9266            },
 9267        );
 9268        let save = editor
 9269            .update_in(cx, |editor, window, cx| {
 9270                editor.save(
 9271                    SaveOptions {
 9272                        format: true,
 9273                        autosave: false,
 9274                    },
 9275                    project.clone(),
 9276                    window,
 9277                    cx,
 9278                )
 9279            })
 9280            .unwrap();
 9281        cx.executor().start_waiting();
 9282        save.await;
 9283
 9284        assert_eq!(
 9285            editor.update(cx, |editor, cx| editor.text(cx)),
 9286            "one, two\nthree\n"
 9287        );
 9288        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9289    }
 9290
 9291    {
 9292        editor.update_in(cx, |editor, window, cx| {
 9293            editor.set_text("one\ntwo\nthree\n", window, cx)
 9294        });
 9295        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9296
 9297        // Ensure we can still save even if formatting hangs.
 9298        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9299            move |params, _| async move {
 9300                assert_eq!(
 9301                    params.text_document.uri,
 9302                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9303                );
 9304                futures::future::pending::<()>().await;
 9305                unreachable!()
 9306            },
 9307        );
 9308        let save = editor
 9309            .update_in(cx, |editor, window, cx| {
 9310                editor.save(
 9311                    SaveOptions {
 9312                        format: true,
 9313                        autosave: false,
 9314                    },
 9315                    project.clone(),
 9316                    window,
 9317                    cx,
 9318                )
 9319            })
 9320            .unwrap();
 9321        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9322        cx.executor().start_waiting();
 9323        save.await;
 9324        assert_eq!(
 9325            editor.update(cx, |editor, cx| editor.text(cx)),
 9326            "one\ntwo\nthree\n"
 9327        );
 9328    }
 9329
 9330    // Set rust language override and assert overridden tabsize is sent to language server
 9331    update_test_language_settings(cx, |settings| {
 9332        settings.languages.insert(
 9333            "Rust".into(),
 9334            LanguageSettingsContent {
 9335                tab_size: NonZeroU32::new(8),
 9336                ..Default::default()
 9337            },
 9338        );
 9339    });
 9340
 9341    {
 9342        editor.update_in(cx, |editor, window, cx| {
 9343            editor.set_text("somehting_new\n", window, cx)
 9344        });
 9345        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9346        let _formatting_request_signal = fake_server
 9347            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9348                assert_eq!(
 9349                    params.text_document.uri,
 9350                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9351                );
 9352                assert_eq!(params.options.tab_size, 8);
 9353                Ok(Some(vec![]))
 9354            });
 9355        let save = editor
 9356            .update_in(cx, |editor, window, cx| {
 9357                editor.save(
 9358                    SaveOptions {
 9359                        format: true,
 9360                        autosave: false,
 9361                    },
 9362                    project.clone(),
 9363                    window,
 9364                    cx,
 9365                )
 9366            })
 9367            .unwrap();
 9368        cx.executor().start_waiting();
 9369        save.await;
 9370    }
 9371}
 9372
 9373#[gpui::test]
 9374async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9375    init_test(cx, |_| {});
 9376
 9377    let cols = 4;
 9378    let rows = 10;
 9379    let sample_text_1 = sample_text(rows, cols, 'a');
 9380    assert_eq!(
 9381        sample_text_1,
 9382        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9383    );
 9384    let sample_text_2 = sample_text(rows, cols, 'l');
 9385    assert_eq!(
 9386        sample_text_2,
 9387        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9388    );
 9389    let sample_text_3 = sample_text(rows, cols, 'v');
 9390    assert_eq!(
 9391        sample_text_3,
 9392        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9393    );
 9394
 9395    let fs = FakeFs::new(cx.executor());
 9396    fs.insert_tree(
 9397        path!("/a"),
 9398        json!({
 9399            "main.rs": sample_text_1,
 9400            "other.rs": sample_text_2,
 9401            "lib.rs": sample_text_3,
 9402        }),
 9403    )
 9404    .await;
 9405
 9406    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9407    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9408    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9409
 9410    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9411    language_registry.add(rust_lang());
 9412    let mut fake_servers = language_registry.register_fake_lsp(
 9413        "Rust",
 9414        FakeLspAdapter {
 9415            capabilities: lsp::ServerCapabilities {
 9416                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9417                ..Default::default()
 9418            },
 9419            ..Default::default()
 9420        },
 9421    );
 9422
 9423    let worktree = project.update(cx, |project, cx| {
 9424        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9425        assert_eq!(worktrees.len(), 1);
 9426        worktrees.pop().unwrap()
 9427    });
 9428    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9429
 9430    let buffer_1 = project
 9431        .update(cx, |project, cx| {
 9432            project.open_buffer((worktree_id, "main.rs"), cx)
 9433        })
 9434        .await
 9435        .unwrap();
 9436    let buffer_2 = project
 9437        .update(cx, |project, cx| {
 9438            project.open_buffer((worktree_id, "other.rs"), cx)
 9439        })
 9440        .await
 9441        .unwrap();
 9442    let buffer_3 = project
 9443        .update(cx, |project, cx| {
 9444            project.open_buffer((worktree_id, "lib.rs"), cx)
 9445        })
 9446        .await
 9447        .unwrap();
 9448
 9449    let multi_buffer = cx.new(|cx| {
 9450        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9451        multi_buffer.push_excerpts(
 9452            buffer_1.clone(),
 9453            [
 9454                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9455                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9456                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9457            ],
 9458            cx,
 9459        );
 9460        multi_buffer.push_excerpts(
 9461            buffer_2.clone(),
 9462            [
 9463                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9464                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9465                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9466            ],
 9467            cx,
 9468        );
 9469        multi_buffer.push_excerpts(
 9470            buffer_3.clone(),
 9471            [
 9472                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9473                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9474                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9475            ],
 9476            cx,
 9477        );
 9478        multi_buffer
 9479    });
 9480    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9481        Editor::new(
 9482            EditorMode::full(),
 9483            multi_buffer,
 9484            Some(project.clone()),
 9485            window,
 9486            cx,
 9487        )
 9488    });
 9489
 9490    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9491        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9492            s.select_ranges(Some(1..2))
 9493        });
 9494        editor.insert("|one|two|three|", window, cx);
 9495    });
 9496    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9497    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9498        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9499            s.select_ranges(Some(60..70))
 9500        });
 9501        editor.insert("|four|five|six|", window, cx);
 9502    });
 9503    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9504
 9505    // First two buffers should be edited, but not the third one.
 9506    assert_eq!(
 9507        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9508        "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}",
 9509    );
 9510    buffer_1.update(cx, |buffer, _| {
 9511        assert!(buffer.is_dirty());
 9512        assert_eq!(
 9513            buffer.text(),
 9514            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9515        )
 9516    });
 9517    buffer_2.update(cx, |buffer, _| {
 9518        assert!(buffer.is_dirty());
 9519        assert_eq!(
 9520            buffer.text(),
 9521            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9522        )
 9523    });
 9524    buffer_3.update(cx, |buffer, _| {
 9525        assert!(!buffer.is_dirty());
 9526        assert_eq!(buffer.text(), sample_text_3,)
 9527    });
 9528    cx.executor().run_until_parked();
 9529
 9530    cx.executor().start_waiting();
 9531    let save = multi_buffer_editor
 9532        .update_in(cx, |editor, window, cx| {
 9533            editor.save(
 9534                SaveOptions {
 9535                    format: true,
 9536                    autosave: false,
 9537                },
 9538                project.clone(),
 9539                window,
 9540                cx,
 9541            )
 9542        })
 9543        .unwrap();
 9544
 9545    let fake_server = fake_servers.next().await.unwrap();
 9546    fake_server
 9547        .server
 9548        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9549            Ok(Some(vec![lsp::TextEdit::new(
 9550                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9551                format!("[{} formatted]", params.text_document.uri),
 9552            )]))
 9553        })
 9554        .detach();
 9555    save.await;
 9556
 9557    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9558    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9559    assert_eq!(
 9560        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9561        uri!(
 9562            "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}"
 9563        ),
 9564    );
 9565    buffer_1.update(cx, |buffer, _| {
 9566        assert!(!buffer.is_dirty());
 9567        assert_eq!(
 9568            buffer.text(),
 9569            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9570        )
 9571    });
 9572    buffer_2.update(cx, |buffer, _| {
 9573        assert!(!buffer.is_dirty());
 9574        assert_eq!(
 9575            buffer.text(),
 9576            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9577        )
 9578    });
 9579    buffer_3.update(cx, |buffer, _| {
 9580        assert!(!buffer.is_dirty());
 9581        assert_eq!(buffer.text(), sample_text_3,)
 9582    });
 9583}
 9584
 9585#[gpui::test]
 9586async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9587    init_test(cx, |_| {});
 9588
 9589    let fs = FakeFs::new(cx.executor());
 9590    fs.insert_tree(
 9591        path!("/dir"),
 9592        json!({
 9593            "file1.rs": "fn main() { println!(\"hello\"); }",
 9594            "file2.rs": "fn test() { println!(\"test\"); }",
 9595            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9596        }),
 9597    )
 9598    .await;
 9599
 9600    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9601    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9602    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9603
 9604    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9605    language_registry.add(rust_lang());
 9606
 9607    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9608    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9609
 9610    // Open three buffers
 9611    let buffer_1 = project
 9612        .update(cx, |project, cx| {
 9613            project.open_buffer((worktree_id, "file1.rs"), cx)
 9614        })
 9615        .await
 9616        .unwrap();
 9617    let buffer_2 = project
 9618        .update(cx, |project, cx| {
 9619            project.open_buffer((worktree_id, "file2.rs"), cx)
 9620        })
 9621        .await
 9622        .unwrap();
 9623    let buffer_3 = project
 9624        .update(cx, |project, cx| {
 9625            project.open_buffer((worktree_id, "file3.rs"), cx)
 9626        })
 9627        .await
 9628        .unwrap();
 9629
 9630    // Create a multi-buffer with all three buffers
 9631    let multi_buffer = cx.new(|cx| {
 9632        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9633        multi_buffer.push_excerpts(
 9634            buffer_1.clone(),
 9635            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9636            cx,
 9637        );
 9638        multi_buffer.push_excerpts(
 9639            buffer_2.clone(),
 9640            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9641            cx,
 9642        );
 9643        multi_buffer.push_excerpts(
 9644            buffer_3.clone(),
 9645            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9646            cx,
 9647        );
 9648        multi_buffer
 9649    });
 9650
 9651    let editor = cx.new_window_entity(|window, cx| {
 9652        Editor::new(
 9653            EditorMode::full(),
 9654            multi_buffer,
 9655            Some(project.clone()),
 9656            window,
 9657            cx,
 9658        )
 9659    });
 9660
 9661    // Edit only the first buffer
 9662    editor.update_in(cx, |editor, window, cx| {
 9663        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9664            s.select_ranges(Some(10..10))
 9665        });
 9666        editor.insert("// edited", window, cx);
 9667    });
 9668
 9669    // Verify that only buffer 1 is dirty
 9670    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9671    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9672    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9673
 9674    // Get write counts after file creation (files were created with initial content)
 9675    // We expect each file to have been written once during creation
 9676    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9677    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9678    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9679
 9680    // Perform autosave
 9681    let save_task = editor.update_in(cx, |editor, window, cx| {
 9682        editor.save(
 9683            SaveOptions {
 9684                format: true,
 9685                autosave: true,
 9686            },
 9687            project.clone(),
 9688            window,
 9689            cx,
 9690        )
 9691    });
 9692    save_task.await.unwrap();
 9693
 9694    // Only the dirty buffer should have been saved
 9695    assert_eq!(
 9696        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9697        1,
 9698        "Buffer 1 was dirty, so it should have been written once during autosave"
 9699    );
 9700    assert_eq!(
 9701        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9702        0,
 9703        "Buffer 2 was clean, so it should not have been written during autosave"
 9704    );
 9705    assert_eq!(
 9706        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9707        0,
 9708        "Buffer 3 was clean, so it should not have been written during autosave"
 9709    );
 9710
 9711    // Verify buffer states after autosave
 9712    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9713    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9714    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9715
 9716    // Now perform a manual save (format = true)
 9717    let save_task = editor.update_in(cx, |editor, window, cx| {
 9718        editor.save(
 9719            SaveOptions {
 9720                format: true,
 9721                autosave: false,
 9722            },
 9723            project.clone(),
 9724            window,
 9725            cx,
 9726        )
 9727    });
 9728    save_task.await.unwrap();
 9729
 9730    // During manual save, clean buffers don't get written to disk
 9731    // They just get did_save called for language server notifications
 9732    assert_eq!(
 9733        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9734        1,
 9735        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9736    );
 9737    assert_eq!(
 9738        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9739        0,
 9740        "Buffer 2 should not have been written at all"
 9741    );
 9742    assert_eq!(
 9743        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9744        0,
 9745        "Buffer 3 should not have been written at all"
 9746    );
 9747}
 9748
 9749#[gpui::test]
 9750async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9751    init_test(cx, |_| {});
 9752
 9753    let fs = FakeFs::new(cx.executor());
 9754    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9755
 9756    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9757
 9758    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9759    language_registry.add(rust_lang());
 9760    let mut fake_servers = language_registry.register_fake_lsp(
 9761        "Rust",
 9762        FakeLspAdapter {
 9763            capabilities: lsp::ServerCapabilities {
 9764                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9765                ..Default::default()
 9766            },
 9767            ..Default::default()
 9768        },
 9769    );
 9770
 9771    let buffer = project
 9772        .update(cx, |project, cx| {
 9773            project.open_local_buffer(path!("/file.rs"), cx)
 9774        })
 9775        .await
 9776        .unwrap();
 9777
 9778    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9779    let (editor, cx) = cx.add_window_view(|window, cx| {
 9780        build_editor_with_project(project.clone(), buffer, window, cx)
 9781    });
 9782    editor.update_in(cx, |editor, window, cx| {
 9783        editor.set_text("one\ntwo\nthree\n", window, cx)
 9784    });
 9785    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9786
 9787    cx.executor().start_waiting();
 9788    let fake_server = fake_servers.next().await.unwrap();
 9789
 9790    let save = editor
 9791        .update_in(cx, |editor, window, cx| {
 9792            editor.save(
 9793                SaveOptions {
 9794                    format: true,
 9795                    autosave: false,
 9796                },
 9797                project.clone(),
 9798                window,
 9799                cx,
 9800            )
 9801        })
 9802        .unwrap();
 9803    fake_server
 9804        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9805            assert_eq!(
 9806                params.text_document.uri,
 9807                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9808            );
 9809            assert_eq!(params.options.tab_size, 4);
 9810            Ok(Some(vec![lsp::TextEdit::new(
 9811                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9812                ", ".to_string(),
 9813            )]))
 9814        })
 9815        .next()
 9816        .await;
 9817    cx.executor().start_waiting();
 9818    save.await;
 9819    assert_eq!(
 9820        editor.update(cx, |editor, cx| editor.text(cx)),
 9821        "one, two\nthree\n"
 9822    );
 9823    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9824
 9825    editor.update_in(cx, |editor, window, cx| {
 9826        editor.set_text("one\ntwo\nthree\n", window, cx)
 9827    });
 9828    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9829
 9830    // Ensure we can still save even if formatting hangs.
 9831    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9832        move |params, _| async move {
 9833            assert_eq!(
 9834                params.text_document.uri,
 9835                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9836            );
 9837            futures::future::pending::<()>().await;
 9838            unreachable!()
 9839        },
 9840    );
 9841    let save = editor
 9842        .update_in(cx, |editor, window, cx| {
 9843            editor.save(
 9844                SaveOptions {
 9845                    format: true,
 9846                    autosave: false,
 9847                },
 9848                project.clone(),
 9849                window,
 9850                cx,
 9851            )
 9852        })
 9853        .unwrap();
 9854    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9855    cx.executor().start_waiting();
 9856    save.await;
 9857    assert_eq!(
 9858        editor.update(cx, |editor, cx| editor.text(cx)),
 9859        "one\ntwo\nthree\n"
 9860    );
 9861    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9862
 9863    // For non-dirty buffer, no formatting request should be sent
 9864    let save = editor
 9865        .update_in(cx, |editor, window, cx| {
 9866            editor.save(
 9867                SaveOptions {
 9868                    format: false,
 9869                    autosave: false,
 9870                },
 9871                project.clone(),
 9872                window,
 9873                cx,
 9874            )
 9875        })
 9876        .unwrap();
 9877    let _pending_format_request = fake_server
 9878        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9879            panic!("Should not be invoked");
 9880        })
 9881        .next();
 9882    cx.executor().start_waiting();
 9883    save.await;
 9884
 9885    // Set Rust language override and assert overridden tabsize is sent to language server
 9886    update_test_language_settings(cx, |settings| {
 9887        settings.languages.insert(
 9888            "Rust".into(),
 9889            LanguageSettingsContent {
 9890                tab_size: NonZeroU32::new(8),
 9891                ..Default::default()
 9892            },
 9893        );
 9894    });
 9895
 9896    editor.update_in(cx, |editor, window, cx| {
 9897        editor.set_text("somehting_new\n", window, cx)
 9898    });
 9899    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9900    let save = editor
 9901        .update_in(cx, |editor, window, cx| {
 9902            editor.save(
 9903                SaveOptions {
 9904                    format: true,
 9905                    autosave: false,
 9906                },
 9907                project.clone(),
 9908                window,
 9909                cx,
 9910            )
 9911        })
 9912        .unwrap();
 9913    fake_server
 9914        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9915            assert_eq!(
 9916                params.text_document.uri,
 9917                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9918            );
 9919            assert_eq!(params.options.tab_size, 8);
 9920            Ok(Some(Vec::new()))
 9921        })
 9922        .next()
 9923        .await;
 9924    save.await;
 9925}
 9926
 9927#[gpui::test]
 9928async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9929    init_test(cx, |settings| {
 9930        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9931            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9932        ))
 9933    });
 9934
 9935    let fs = FakeFs::new(cx.executor());
 9936    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9937
 9938    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9939
 9940    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9941    language_registry.add(Arc::new(Language::new(
 9942        LanguageConfig {
 9943            name: "Rust".into(),
 9944            matcher: LanguageMatcher {
 9945                path_suffixes: vec!["rs".to_string()],
 9946                ..Default::default()
 9947            },
 9948            ..LanguageConfig::default()
 9949        },
 9950        Some(tree_sitter_rust::LANGUAGE.into()),
 9951    )));
 9952    update_test_language_settings(cx, |settings| {
 9953        // Enable Prettier formatting for the same buffer, and ensure
 9954        // LSP is called instead of Prettier.
 9955        settings.defaults.prettier = Some(PrettierSettings {
 9956            allowed: true,
 9957            ..PrettierSettings::default()
 9958        });
 9959    });
 9960    let mut fake_servers = language_registry.register_fake_lsp(
 9961        "Rust",
 9962        FakeLspAdapter {
 9963            capabilities: lsp::ServerCapabilities {
 9964                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9965                ..Default::default()
 9966            },
 9967            ..Default::default()
 9968        },
 9969    );
 9970
 9971    let buffer = project
 9972        .update(cx, |project, cx| {
 9973            project.open_local_buffer(path!("/file.rs"), cx)
 9974        })
 9975        .await
 9976        .unwrap();
 9977
 9978    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9979    let (editor, cx) = cx.add_window_view(|window, cx| {
 9980        build_editor_with_project(project.clone(), buffer, window, cx)
 9981    });
 9982    editor.update_in(cx, |editor, window, cx| {
 9983        editor.set_text("one\ntwo\nthree\n", window, cx)
 9984    });
 9985
 9986    cx.executor().start_waiting();
 9987    let fake_server = fake_servers.next().await.unwrap();
 9988
 9989    let format = editor
 9990        .update_in(cx, |editor, window, cx| {
 9991            editor.perform_format(
 9992                project.clone(),
 9993                FormatTrigger::Manual,
 9994                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9995                window,
 9996                cx,
 9997            )
 9998        })
 9999        .unwrap();
10000    fake_server
10001        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10002            assert_eq!(
10003                params.text_document.uri,
10004                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10005            );
10006            assert_eq!(params.options.tab_size, 4);
10007            Ok(Some(vec![lsp::TextEdit::new(
10008                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10009                ", ".to_string(),
10010            )]))
10011        })
10012        .next()
10013        .await;
10014    cx.executor().start_waiting();
10015    format.await;
10016    assert_eq!(
10017        editor.update(cx, |editor, cx| editor.text(cx)),
10018        "one, two\nthree\n"
10019    );
10020
10021    editor.update_in(cx, |editor, window, cx| {
10022        editor.set_text("one\ntwo\nthree\n", window, cx)
10023    });
10024    // Ensure we don't lock if formatting hangs.
10025    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10026        move |params, _| async move {
10027            assert_eq!(
10028                params.text_document.uri,
10029                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10030            );
10031            futures::future::pending::<()>().await;
10032            unreachable!()
10033        },
10034    );
10035    let format = editor
10036        .update_in(cx, |editor, window, cx| {
10037            editor.perform_format(
10038                project,
10039                FormatTrigger::Manual,
10040                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10041                window,
10042                cx,
10043            )
10044        })
10045        .unwrap();
10046    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10047    cx.executor().start_waiting();
10048    format.await;
10049    assert_eq!(
10050        editor.update(cx, |editor, cx| editor.text(cx)),
10051        "one\ntwo\nthree\n"
10052    );
10053}
10054
10055#[gpui::test]
10056async fn test_multiple_formatters(cx: &mut TestAppContext) {
10057    init_test(cx, |settings| {
10058        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10059        settings.defaults.formatter =
10060            Some(language_settings::SelectedFormatter::List(FormatterList(
10061                vec![
10062                    Formatter::LanguageServer { name: None },
10063                    Formatter::CodeActions(
10064                        [
10065                            ("code-action-1".into(), true),
10066                            ("code-action-2".into(), true),
10067                        ]
10068                        .into_iter()
10069                        .collect(),
10070                    ),
10071                ]
10072                .into(),
10073            )))
10074    });
10075
10076    let fs = FakeFs::new(cx.executor());
10077    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10078        .await;
10079
10080    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10081    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10082    language_registry.add(rust_lang());
10083
10084    let mut fake_servers = language_registry.register_fake_lsp(
10085        "Rust",
10086        FakeLspAdapter {
10087            capabilities: lsp::ServerCapabilities {
10088                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10089                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10090                    commands: vec!["the-command-for-code-action-1".into()],
10091                    ..Default::default()
10092                }),
10093                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10094                ..Default::default()
10095            },
10096            ..Default::default()
10097        },
10098    );
10099
10100    let buffer = project
10101        .update(cx, |project, cx| {
10102            project.open_local_buffer(path!("/file.rs"), cx)
10103        })
10104        .await
10105        .unwrap();
10106
10107    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10108    let (editor, cx) = cx.add_window_view(|window, cx| {
10109        build_editor_with_project(project.clone(), buffer, window, cx)
10110    });
10111
10112    cx.executor().start_waiting();
10113
10114    let fake_server = fake_servers.next().await.unwrap();
10115    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10116        move |_params, _| async move {
10117            Ok(Some(vec![lsp::TextEdit::new(
10118                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10119                "applied-formatting\n".to_string(),
10120            )]))
10121        },
10122    );
10123    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10124        move |params, _| async move {
10125            assert_eq!(
10126                params.context.only,
10127                Some(vec!["code-action-1".into(), "code-action-2".into()])
10128            );
10129            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10130            Ok(Some(vec![
10131                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10132                    kind: Some("code-action-1".into()),
10133                    edit: Some(lsp::WorkspaceEdit::new(
10134                        [(
10135                            uri.clone(),
10136                            vec![lsp::TextEdit::new(
10137                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10138                                "applied-code-action-1-edit\n".to_string(),
10139                            )],
10140                        )]
10141                        .into_iter()
10142                        .collect(),
10143                    )),
10144                    command: Some(lsp::Command {
10145                        command: "the-command-for-code-action-1".into(),
10146                        ..Default::default()
10147                    }),
10148                    ..Default::default()
10149                }),
10150                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10151                    kind: Some("code-action-2".into()),
10152                    edit: Some(lsp::WorkspaceEdit::new(
10153                        [(
10154                            uri.clone(),
10155                            vec![lsp::TextEdit::new(
10156                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10157                                "applied-code-action-2-edit\n".to_string(),
10158                            )],
10159                        )]
10160                        .into_iter()
10161                        .collect(),
10162                    )),
10163                    ..Default::default()
10164                }),
10165            ]))
10166        },
10167    );
10168
10169    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10170        move |params, _| async move { Ok(params) }
10171    });
10172
10173    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10174    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10175        let fake = fake_server.clone();
10176        let lock = command_lock.clone();
10177        move |params, _| {
10178            assert_eq!(params.command, "the-command-for-code-action-1");
10179            let fake = fake.clone();
10180            let lock = lock.clone();
10181            async move {
10182                lock.lock().await;
10183                fake.server
10184                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10185                        label: None,
10186                        edit: lsp::WorkspaceEdit {
10187                            changes: Some(
10188                                [(
10189                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10190                                    vec![lsp::TextEdit {
10191                                        range: lsp::Range::new(
10192                                            lsp::Position::new(0, 0),
10193                                            lsp::Position::new(0, 0),
10194                                        ),
10195                                        new_text: "applied-code-action-1-command\n".into(),
10196                                    }],
10197                                )]
10198                                .into_iter()
10199                                .collect(),
10200                            ),
10201                            ..Default::default()
10202                        },
10203                    })
10204                    .await
10205                    .into_response()
10206                    .unwrap();
10207                Ok(Some(json!(null)))
10208            }
10209        }
10210    });
10211
10212    cx.executor().start_waiting();
10213    editor
10214        .update_in(cx, |editor, window, cx| {
10215            editor.perform_format(
10216                project.clone(),
10217                FormatTrigger::Manual,
10218                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10219                window,
10220                cx,
10221            )
10222        })
10223        .unwrap()
10224        .await;
10225    editor.update(cx, |editor, cx| {
10226        assert_eq!(
10227            editor.text(cx),
10228            r#"
10229                applied-code-action-2-edit
10230                applied-code-action-1-command
10231                applied-code-action-1-edit
10232                applied-formatting
10233                one
10234                two
10235                three
10236            "#
10237            .unindent()
10238        );
10239    });
10240
10241    editor.update_in(cx, |editor, window, cx| {
10242        editor.undo(&Default::default(), window, cx);
10243        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10244    });
10245
10246    // Perform a manual edit while waiting for an LSP command
10247    // that's being run as part of a formatting code action.
10248    let lock_guard = command_lock.lock().await;
10249    let format = editor
10250        .update_in(cx, |editor, window, cx| {
10251            editor.perform_format(
10252                project.clone(),
10253                FormatTrigger::Manual,
10254                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10255                window,
10256                cx,
10257            )
10258        })
10259        .unwrap();
10260    cx.run_until_parked();
10261    editor.update(cx, |editor, cx| {
10262        assert_eq!(
10263            editor.text(cx),
10264            r#"
10265                applied-code-action-1-edit
10266                applied-formatting
10267                one
10268                two
10269                three
10270            "#
10271            .unindent()
10272        );
10273
10274        editor.buffer.update(cx, |buffer, cx| {
10275            let ix = buffer.len(cx);
10276            buffer.edit([(ix..ix, "edited\n")], None, cx);
10277        });
10278    });
10279
10280    // Allow the LSP command to proceed. Because the buffer was edited,
10281    // the second code action will not be run.
10282    drop(lock_guard);
10283    format.await;
10284    editor.update_in(cx, |editor, window, cx| {
10285        assert_eq!(
10286            editor.text(cx),
10287            r#"
10288                applied-code-action-1-command
10289                applied-code-action-1-edit
10290                applied-formatting
10291                one
10292                two
10293                three
10294                edited
10295            "#
10296            .unindent()
10297        );
10298
10299        // The manual edit is undone first, because it is the last thing the user did
10300        // (even though the command completed afterwards).
10301        editor.undo(&Default::default(), window, cx);
10302        assert_eq!(
10303            editor.text(cx),
10304            r#"
10305                applied-code-action-1-command
10306                applied-code-action-1-edit
10307                applied-formatting
10308                one
10309                two
10310                three
10311            "#
10312            .unindent()
10313        );
10314
10315        // All the formatting (including the command, which completed after the manual edit)
10316        // is undone together.
10317        editor.undo(&Default::default(), window, cx);
10318        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10319    });
10320}
10321
10322#[gpui::test]
10323async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10324    init_test(cx, |settings| {
10325        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10326            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
10327        ))
10328    });
10329
10330    let fs = FakeFs::new(cx.executor());
10331    fs.insert_file(path!("/file.ts"), Default::default()).await;
10332
10333    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10334
10335    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10336    language_registry.add(Arc::new(Language::new(
10337        LanguageConfig {
10338            name: "TypeScript".into(),
10339            matcher: LanguageMatcher {
10340                path_suffixes: vec!["ts".to_string()],
10341                ..Default::default()
10342            },
10343            ..LanguageConfig::default()
10344        },
10345        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10346    )));
10347    update_test_language_settings(cx, |settings| {
10348        settings.defaults.prettier = Some(PrettierSettings {
10349            allowed: true,
10350            ..PrettierSettings::default()
10351        });
10352    });
10353    let mut fake_servers = language_registry.register_fake_lsp(
10354        "TypeScript",
10355        FakeLspAdapter {
10356            capabilities: lsp::ServerCapabilities {
10357                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10358                ..Default::default()
10359            },
10360            ..Default::default()
10361        },
10362    );
10363
10364    let buffer = project
10365        .update(cx, |project, cx| {
10366            project.open_local_buffer(path!("/file.ts"), cx)
10367        })
10368        .await
10369        .unwrap();
10370
10371    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10372    let (editor, cx) = cx.add_window_view(|window, cx| {
10373        build_editor_with_project(project.clone(), buffer, window, cx)
10374    });
10375    editor.update_in(cx, |editor, window, cx| {
10376        editor.set_text(
10377            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10378            window,
10379            cx,
10380        )
10381    });
10382
10383    cx.executor().start_waiting();
10384    let fake_server = fake_servers.next().await.unwrap();
10385
10386    let format = editor
10387        .update_in(cx, |editor, window, cx| {
10388            editor.perform_code_action_kind(
10389                project.clone(),
10390                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10391                window,
10392                cx,
10393            )
10394        })
10395        .unwrap();
10396    fake_server
10397        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10398            assert_eq!(
10399                params.text_document.uri,
10400                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10401            );
10402            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10403                lsp::CodeAction {
10404                    title: "Organize Imports".to_string(),
10405                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10406                    edit: Some(lsp::WorkspaceEdit {
10407                        changes: Some(
10408                            [(
10409                                params.text_document.uri.clone(),
10410                                vec![lsp::TextEdit::new(
10411                                    lsp::Range::new(
10412                                        lsp::Position::new(1, 0),
10413                                        lsp::Position::new(2, 0),
10414                                    ),
10415                                    "".to_string(),
10416                                )],
10417                            )]
10418                            .into_iter()
10419                            .collect(),
10420                        ),
10421                        ..Default::default()
10422                    }),
10423                    ..Default::default()
10424                },
10425            )]))
10426        })
10427        .next()
10428        .await;
10429    cx.executor().start_waiting();
10430    format.await;
10431    assert_eq!(
10432        editor.update(cx, |editor, cx| editor.text(cx)),
10433        "import { a } from 'module';\n\nconst x = a;\n"
10434    );
10435
10436    editor.update_in(cx, |editor, window, cx| {
10437        editor.set_text(
10438            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10439            window,
10440            cx,
10441        )
10442    });
10443    // Ensure we don't lock if code action hangs.
10444    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10445        move |params, _| async move {
10446            assert_eq!(
10447                params.text_document.uri,
10448                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10449            );
10450            futures::future::pending::<()>().await;
10451            unreachable!()
10452        },
10453    );
10454    let format = editor
10455        .update_in(cx, |editor, window, cx| {
10456            editor.perform_code_action_kind(
10457                project,
10458                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10459                window,
10460                cx,
10461            )
10462        })
10463        .unwrap();
10464    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10465    cx.executor().start_waiting();
10466    format.await;
10467    assert_eq!(
10468        editor.update(cx, |editor, cx| editor.text(cx)),
10469        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10470    );
10471}
10472
10473#[gpui::test]
10474async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10475    init_test(cx, |_| {});
10476
10477    let mut cx = EditorLspTestContext::new_rust(
10478        lsp::ServerCapabilities {
10479            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10480            ..Default::default()
10481        },
10482        cx,
10483    )
10484    .await;
10485
10486    cx.set_state(indoc! {"
10487        one.twoˇ
10488    "});
10489
10490    // The format request takes a long time. When it completes, it inserts
10491    // a newline and an indent before the `.`
10492    cx.lsp
10493        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10494            let executor = cx.background_executor().clone();
10495            async move {
10496                executor.timer(Duration::from_millis(100)).await;
10497                Ok(Some(vec![lsp::TextEdit {
10498                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10499                    new_text: "\n    ".into(),
10500                }]))
10501            }
10502        });
10503
10504    // Submit a format request.
10505    let format_1 = cx
10506        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10507        .unwrap();
10508    cx.executor().run_until_parked();
10509
10510    // Submit a second format request.
10511    let format_2 = cx
10512        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10513        .unwrap();
10514    cx.executor().run_until_parked();
10515
10516    // Wait for both format requests to complete
10517    cx.executor().advance_clock(Duration::from_millis(200));
10518    cx.executor().start_waiting();
10519    format_1.await.unwrap();
10520    cx.executor().start_waiting();
10521    format_2.await.unwrap();
10522
10523    // The formatting edits only happens once.
10524    cx.assert_editor_state(indoc! {"
10525        one
10526            .twoˇ
10527    "});
10528}
10529
10530#[gpui::test]
10531async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10532    init_test(cx, |settings| {
10533        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10534    });
10535
10536    let mut cx = EditorLspTestContext::new_rust(
10537        lsp::ServerCapabilities {
10538            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10539            ..Default::default()
10540        },
10541        cx,
10542    )
10543    .await;
10544
10545    // Set up a buffer white some trailing whitespace and no trailing newline.
10546    cx.set_state(
10547        &[
10548            "one ",   //
10549            "twoˇ",   //
10550            "three ", //
10551            "four",   //
10552        ]
10553        .join("\n"),
10554    );
10555
10556    // Submit a format request.
10557    let format = cx
10558        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10559        .unwrap();
10560
10561    // Record which buffer changes have been sent to the language server
10562    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10563    cx.lsp
10564        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10565            let buffer_changes = buffer_changes.clone();
10566            move |params, _| {
10567                buffer_changes.lock().extend(
10568                    params
10569                        .content_changes
10570                        .into_iter()
10571                        .map(|e| (e.range.unwrap(), e.text)),
10572                );
10573            }
10574        });
10575
10576    // Handle formatting requests to the language server.
10577    cx.lsp
10578        .set_request_handler::<lsp::request::Formatting, _, _>({
10579            let buffer_changes = buffer_changes.clone();
10580            move |_, _| {
10581                // When formatting is requested, trailing whitespace has already been stripped,
10582                // and the trailing newline has already been added.
10583                assert_eq!(
10584                    &buffer_changes.lock()[1..],
10585                    &[
10586                        (
10587                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10588                            "".into()
10589                        ),
10590                        (
10591                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10592                            "".into()
10593                        ),
10594                        (
10595                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10596                            "\n".into()
10597                        ),
10598                    ]
10599                );
10600
10601                // Insert blank lines between each line of the buffer.
10602                async move {
10603                    Ok(Some(vec![
10604                        lsp::TextEdit {
10605                            range: lsp::Range::new(
10606                                lsp::Position::new(1, 0),
10607                                lsp::Position::new(1, 0),
10608                            ),
10609                            new_text: "\n".into(),
10610                        },
10611                        lsp::TextEdit {
10612                            range: lsp::Range::new(
10613                                lsp::Position::new(2, 0),
10614                                lsp::Position::new(2, 0),
10615                            ),
10616                            new_text: "\n".into(),
10617                        },
10618                    ]))
10619                }
10620            }
10621        });
10622
10623    // After formatting the buffer, the trailing whitespace is stripped,
10624    // a newline is appended, and the edits provided by the language server
10625    // have been applied.
10626    format.await.unwrap();
10627    cx.assert_editor_state(
10628        &[
10629            "one",   //
10630            "",      //
10631            "twoˇ",  //
10632            "",      //
10633            "three", //
10634            "four",  //
10635            "",      //
10636        ]
10637        .join("\n"),
10638    );
10639
10640    // Undoing the formatting undoes the trailing whitespace removal, the
10641    // trailing newline, and the LSP edits.
10642    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10643    cx.assert_editor_state(
10644        &[
10645            "one ",   //
10646            "twoˇ",   //
10647            "three ", //
10648            "four",   //
10649        ]
10650        .join("\n"),
10651    );
10652}
10653
10654#[gpui::test]
10655async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10656    cx: &mut TestAppContext,
10657) {
10658    init_test(cx, |_| {});
10659
10660    cx.update(|cx| {
10661        cx.update_global::<SettingsStore, _>(|settings, cx| {
10662            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10663                settings.auto_signature_help = Some(true);
10664            });
10665        });
10666    });
10667
10668    let mut cx = EditorLspTestContext::new_rust(
10669        lsp::ServerCapabilities {
10670            signature_help_provider: Some(lsp::SignatureHelpOptions {
10671                ..Default::default()
10672            }),
10673            ..Default::default()
10674        },
10675        cx,
10676    )
10677    .await;
10678
10679    let language = Language::new(
10680        LanguageConfig {
10681            name: "Rust".into(),
10682            brackets: BracketPairConfig {
10683                pairs: vec![
10684                    BracketPair {
10685                        start: "{".to_string(),
10686                        end: "}".to_string(),
10687                        close: true,
10688                        surround: true,
10689                        newline: true,
10690                    },
10691                    BracketPair {
10692                        start: "(".to_string(),
10693                        end: ")".to_string(),
10694                        close: true,
10695                        surround: true,
10696                        newline: true,
10697                    },
10698                    BracketPair {
10699                        start: "/*".to_string(),
10700                        end: " */".to_string(),
10701                        close: true,
10702                        surround: true,
10703                        newline: true,
10704                    },
10705                    BracketPair {
10706                        start: "[".to_string(),
10707                        end: "]".to_string(),
10708                        close: false,
10709                        surround: false,
10710                        newline: true,
10711                    },
10712                    BracketPair {
10713                        start: "\"".to_string(),
10714                        end: "\"".to_string(),
10715                        close: true,
10716                        surround: true,
10717                        newline: false,
10718                    },
10719                    BracketPair {
10720                        start: "<".to_string(),
10721                        end: ">".to_string(),
10722                        close: false,
10723                        surround: true,
10724                        newline: true,
10725                    },
10726                ],
10727                ..Default::default()
10728            },
10729            autoclose_before: "})]".to_string(),
10730            ..Default::default()
10731        },
10732        Some(tree_sitter_rust::LANGUAGE.into()),
10733    );
10734    let language = Arc::new(language);
10735
10736    cx.language_registry().add(language.clone());
10737    cx.update_buffer(|buffer, cx| {
10738        buffer.set_language(Some(language), cx);
10739    });
10740
10741    cx.set_state(
10742        &r#"
10743            fn main() {
10744                sampleˇ
10745            }
10746        "#
10747        .unindent(),
10748    );
10749
10750    cx.update_editor(|editor, window, cx| {
10751        editor.handle_input("(", window, cx);
10752    });
10753    cx.assert_editor_state(
10754        &"
10755            fn main() {
10756                sample(ˇ)
10757            }
10758        "
10759        .unindent(),
10760    );
10761
10762    let mocked_response = lsp::SignatureHelp {
10763        signatures: vec![lsp::SignatureInformation {
10764            label: "fn sample(param1: u8, param2: u8)".to_string(),
10765            documentation: None,
10766            parameters: Some(vec![
10767                lsp::ParameterInformation {
10768                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10769                    documentation: None,
10770                },
10771                lsp::ParameterInformation {
10772                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10773                    documentation: None,
10774                },
10775            ]),
10776            active_parameter: None,
10777        }],
10778        active_signature: Some(0),
10779        active_parameter: Some(0),
10780    };
10781    handle_signature_help_request(&mut cx, mocked_response).await;
10782
10783    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10784        .await;
10785
10786    cx.editor(|editor, _, _| {
10787        let signature_help_state = editor.signature_help_state.popover().cloned();
10788        assert_eq!(
10789            signature_help_state.unwrap().label,
10790            "param1: u8, param2: u8"
10791        );
10792    });
10793}
10794
10795#[gpui::test]
10796async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10797    init_test(cx, |_| {});
10798
10799    cx.update(|cx| {
10800        cx.update_global::<SettingsStore, _>(|settings, cx| {
10801            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10802                settings.auto_signature_help = Some(false);
10803                settings.show_signature_help_after_edits = Some(false);
10804            });
10805        });
10806    });
10807
10808    let mut cx = EditorLspTestContext::new_rust(
10809        lsp::ServerCapabilities {
10810            signature_help_provider: Some(lsp::SignatureHelpOptions {
10811                ..Default::default()
10812            }),
10813            ..Default::default()
10814        },
10815        cx,
10816    )
10817    .await;
10818
10819    let language = Language::new(
10820        LanguageConfig {
10821            name: "Rust".into(),
10822            brackets: BracketPairConfig {
10823                pairs: vec![
10824                    BracketPair {
10825                        start: "{".to_string(),
10826                        end: "}".to_string(),
10827                        close: true,
10828                        surround: true,
10829                        newline: true,
10830                    },
10831                    BracketPair {
10832                        start: "(".to_string(),
10833                        end: ")".to_string(),
10834                        close: true,
10835                        surround: true,
10836                        newline: true,
10837                    },
10838                    BracketPair {
10839                        start: "/*".to_string(),
10840                        end: " */".to_string(),
10841                        close: true,
10842                        surround: true,
10843                        newline: true,
10844                    },
10845                    BracketPair {
10846                        start: "[".to_string(),
10847                        end: "]".to_string(),
10848                        close: false,
10849                        surround: false,
10850                        newline: true,
10851                    },
10852                    BracketPair {
10853                        start: "\"".to_string(),
10854                        end: "\"".to_string(),
10855                        close: true,
10856                        surround: true,
10857                        newline: false,
10858                    },
10859                    BracketPair {
10860                        start: "<".to_string(),
10861                        end: ">".to_string(),
10862                        close: false,
10863                        surround: true,
10864                        newline: true,
10865                    },
10866                ],
10867                ..Default::default()
10868            },
10869            autoclose_before: "})]".to_string(),
10870            ..Default::default()
10871        },
10872        Some(tree_sitter_rust::LANGUAGE.into()),
10873    );
10874    let language = Arc::new(language);
10875
10876    cx.language_registry().add(language.clone());
10877    cx.update_buffer(|buffer, cx| {
10878        buffer.set_language(Some(language), cx);
10879    });
10880
10881    // Ensure that signature_help is not called when no signature help is enabled.
10882    cx.set_state(
10883        &r#"
10884            fn main() {
10885                sampleˇ
10886            }
10887        "#
10888        .unindent(),
10889    );
10890    cx.update_editor(|editor, window, cx| {
10891        editor.handle_input("(", window, cx);
10892    });
10893    cx.assert_editor_state(
10894        &"
10895            fn main() {
10896                sample(ˇ)
10897            }
10898        "
10899        .unindent(),
10900    );
10901    cx.editor(|editor, _, _| {
10902        assert!(editor.signature_help_state.task().is_none());
10903    });
10904
10905    let mocked_response = lsp::SignatureHelp {
10906        signatures: vec![lsp::SignatureInformation {
10907            label: "fn sample(param1: u8, param2: u8)".to_string(),
10908            documentation: None,
10909            parameters: Some(vec![
10910                lsp::ParameterInformation {
10911                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10912                    documentation: None,
10913                },
10914                lsp::ParameterInformation {
10915                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10916                    documentation: None,
10917                },
10918            ]),
10919            active_parameter: None,
10920        }],
10921        active_signature: Some(0),
10922        active_parameter: Some(0),
10923    };
10924
10925    // Ensure that signature_help is called when enabled afte edits
10926    cx.update(|_, cx| {
10927        cx.update_global::<SettingsStore, _>(|settings, cx| {
10928            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10929                settings.auto_signature_help = Some(false);
10930                settings.show_signature_help_after_edits = Some(true);
10931            });
10932        });
10933    });
10934    cx.set_state(
10935        &r#"
10936            fn main() {
10937                sampleˇ
10938            }
10939        "#
10940        .unindent(),
10941    );
10942    cx.update_editor(|editor, window, cx| {
10943        editor.handle_input("(", window, cx);
10944    });
10945    cx.assert_editor_state(
10946        &"
10947            fn main() {
10948                sample(ˇ)
10949            }
10950        "
10951        .unindent(),
10952    );
10953    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10954    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10955        .await;
10956    cx.update_editor(|editor, _, _| {
10957        let signature_help_state = editor.signature_help_state.popover().cloned();
10958        assert!(signature_help_state.is_some());
10959        assert_eq!(
10960            signature_help_state.unwrap().label,
10961            "param1: u8, param2: u8"
10962        );
10963        editor.signature_help_state = SignatureHelpState::default();
10964    });
10965
10966    // Ensure that signature_help is called when auto signature help override is enabled
10967    cx.update(|_, cx| {
10968        cx.update_global::<SettingsStore, _>(|settings, cx| {
10969            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10970                settings.auto_signature_help = Some(true);
10971                settings.show_signature_help_after_edits = Some(false);
10972            });
10973        });
10974    });
10975    cx.set_state(
10976        &r#"
10977            fn main() {
10978                sampleˇ
10979            }
10980        "#
10981        .unindent(),
10982    );
10983    cx.update_editor(|editor, window, cx| {
10984        editor.handle_input("(", window, cx);
10985    });
10986    cx.assert_editor_state(
10987        &"
10988            fn main() {
10989                sample(ˇ)
10990            }
10991        "
10992        .unindent(),
10993    );
10994    handle_signature_help_request(&mut cx, mocked_response).await;
10995    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10996        .await;
10997    cx.editor(|editor, _, _| {
10998        let signature_help_state = editor.signature_help_state.popover().cloned();
10999        assert!(signature_help_state.is_some());
11000        assert_eq!(
11001            signature_help_state.unwrap().label,
11002            "param1: u8, param2: u8"
11003        );
11004    });
11005}
11006
11007#[gpui::test]
11008async fn test_signature_help(cx: &mut TestAppContext) {
11009    init_test(cx, |_| {});
11010    cx.update(|cx| {
11011        cx.update_global::<SettingsStore, _>(|settings, cx| {
11012            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11013                settings.auto_signature_help = Some(true);
11014            });
11015        });
11016    });
11017
11018    let mut cx = EditorLspTestContext::new_rust(
11019        lsp::ServerCapabilities {
11020            signature_help_provider: Some(lsp::SignatureHelpOptions {
11021                ..Default::default()
11022            }),
11023            ..Default::default()
11024        },
11025        cx,
11026    )
11027    .await;
11028
11029    // A test that directly calls `show_signature_help`
11030    cx.update_editor(|editor, window, cx| {
11031        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11032    });
11033
11034    let mocked_response = lsp::SignatureHelp {
11035        signatures: vec![lsp::SignatureInformation {
11036            label: "fn sample(param1: u8, param2: u8)".to_string(),
11037            documentation: None,
11038            parameters: Some(vec![
11039                lsp::ParameterInformation {
11040                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11041                    documentation: None,
11042                },
11043                lsp::ParameterInformation {
11044                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11045                    documentation: None,
11046                },
11047            ]),
11048            active_parameter: None,
11049        }],
11050        active_signature: Some(0),
11051        active_parameter: Some(0),
11052    };
11053    handle_signature_help_request(&mut cx, mocked_response).await;
11054
11055    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11056        .await;
11057
11058    cx.editor(|editor, _, _| {
11059        let signature_help_state = editor.signature_help_state.popover().cloned();
11060        assert!(signature_help_state.is_some());
11061        assert_eq!(
11062            signature_help_state.unwrap().label,
11063            "param1: u8, param2: u8"
11064        );
11065    });
11066
11067    // When exiting outside from inside the brackets, `signature_help` is closed.
11068    cx.set_state(indoc! {"
11069        fn main() {
11070            sample(ˇ);
11071        }
11072
11073        fn sample(param1: u8, param2: u8) {}
11074    "});
11075
11076    cx.update_editor(|editor, window, cx| {
11077        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11078    });
11079
11080    let mocked_response = lsp::SignatureHelp {
11081        signatures: Vec::new(),
11082        active_signature: None,
11083        active_parameter: None,
11084    };
11085    handle_signature_help_request(&mut cx, mocked_response).await;
11086
11087    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11088        .await;
11089
11090    cx.editor(|editor, _, _| {
11091        assert!(!editor.signature_help_state.is_shown());
11092    });
11093
11094    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11095    cx.set_state(indoc! {"
11096        fn main() {
11097            sample(ˇ);
11098        }
11099
11100        fn sample(param1: u8, param2: u8) {}
11101    "});
11102
11103    let mocked_response = lsp::SignatureHelp {
11104        signatures: vec![lsp::SignatureInformation {
11105            label: "fn sample(param1: u8, param2: u8)".to_string(),
11106            documentation: None,
11107            parameters: Some(vec![
11108                lsp::ParameterInformation {
11109                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11110                    documentation: None,
11111                },
11112                lsp::ParameterInformation {
11113                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11114                    documentation: None,
11115                },
11116            ]),
11117            active_parameter: None,
11118        }],
11119        active_signature: Some(0),
11120        active_parameter: Some(0),
11121    };
11122    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11123    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11124        .await;
11125    cx.editor(|editor, _, _| {
11126        assert!(editor.signature_help_state.is_shown());
11127    });
11128
11129    // Restore the popover with more parameter input
11130    cx.set_state(indoc! {"
11131        fn main() {
11132            sample(param1, param2ˇ);
11133        }
11134
11135        fn sample(param1: u8, param2: u8) {}
11136    "});
11137
11138    let mocked_response = lsp::SignatureHelp {
11139        signatures: vec![lsp::SignatureInformation {
11140            label: "fn sample(param1: u8, param2: u8)".to_string(),
11141            documentation: None,
11142            parameters: Some(vec![
11143                lsp::ParameterInformation {
11144                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11145                    documentation: None,
11146                },
11147                lsp::ParameterInformation {
11148                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11149                    documentation: None,
11150                },
11151            ]),
11152            active_parameter: None,
11153        }],
11154        active_signature: Some(0),
11155        active_parameter: Some(1),
11156    };
11157    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11158    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11159        .await;
11160
11161    // When selecting a range, the popover is gone.
11162    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11163    cx.update_editor(|editor, window, cx| {
11164        editor.change_selections(None, window, cx, |s| {
11165            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11166        })
11167    });
11168    cx.assert_editor_state(indoc! {"
11169        fn main() {
11170            sample(param1, «ˇparam2»);
11171        }
11172
11173        fn sample(param1: u8, param2: u8) {}
11174    "});
11175    cx.editor(|editor, _, _| {
11176        assert!(!editor.signature_help_state.is_shown());
11177    });
11178
11179    // When unselecting again, the popover is back if within the brackets.
11180    cx.update_editor(|editor, window, cx| {
11181        editor.change_selections(None, window, cx, |s| {
11182            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11183        })
11184    });
11185    cx.assert_editor_state(indoc! {"
11186        fn main() {
11187            sample(param1, ˇparam2);
11188        }
11189
11190        fn sample(param1: u8, param2: u8) {}
11191    "});
11192    handle_signature_help_request(&mut cx, mocked_response).await;
11193    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11194        .await;
11195    cx.editor(|editor, _, _| {
11196        assert!(editor.signature_help_state.is_shown());
11197    });
11198
11199    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11200    cx.update_editor(|editor, window, cx| {
11201        editor.change_selections(None, window, cx, |s| {
11202            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11203            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11204        })
11205    });
11206    cx.assert_editor_state(indoc! {"
11207        fn main() {
11208            sample(param1, ˇparam2);
11209        }
11210
11211        fn sample(param1: u8, param2: u8) {}
11212    "});
11213
11214    let mocked_response = lsp::SignatureHelp {
11215        signatures: vec![lsp::SignatureInformation {
11216            label: "fn sample(param1: u8, param2: u8)".to_string(),
11217            documentation: None,
11218            parameters: Some(vec![
11219                lsp::ParameterInformation {
11220                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11221                    documentation: None,
11222                },
11223                lsp::ParameterInformation {
11224                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11225                    documentation: None,
11226                },
11227            ]),
11228            active_parameter: None,
11229        }],
11230        active_signature: Some(0),
11231        active_parameter: Some(1),
11232    };
11233    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11234    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11235        .await;
11236    cx.update_editor(|editor, _, cx| {
11237        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11238    });
11239    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11240        .await;
11241    cx.update_editor(|editor, window, cx| {
11242        editor.change_selections(None, window, cx, |s| {
11243            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11244        })
11245    });
11246    cx.assert_editor_state(indoc! {"
11247        fn main() {
11248            sample(param1, «ˇparam2»);
11249        }
11250
11251        fn sample(param1: u8, param2: u8) {}
11252    "});
11253    cx.update_editor(|editor, window, cx| {
11254        editor.change_selections(None, window, cx, |s| {
11255            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11256        })
11257    });
11258    cx.assert_editor_state(indoc! {"
11259        fn main() {
11260            sample(param1, ˇparam2);
11261        }
11262
11263        fn sample(param1: u8, param2: u8) {}
11264    "});
11265    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11266        .await;
11267}
11268
11269#[gpui::test]
11270async fn test_completion_mode(cx: &mut TestAppContext) {
11271    init_test(cx, |_| {});
11272    let mut cx = EditorLspTestContext::new_rust(
11273        lsp::ServerCapabilities {
11274            completion_provider: Some(lsp::CompletionOptions {
11275                resolve_provider: Some(true),
11276                ..Default::default()
11277            }),
11278            ..Default::default()
11279        },
11280        cx,
11281    )
11282    .await;
11283
11284    struct Run {
11285        run_description: &'static str,
11286        initial_state: String,
11287        buffer_marked_text: String,
11288        completion_label: &'static str,
11289        completion_text: &'static str,
11290        expected_with_insert_mode: String,
11291        expected_with_replace_mode: String,
11292        expected_with_replace_subsequence_mode: String,
11293        expected_with_replace_suffix_mode: String,
11294    }
11295
11296    let runs = [
11297        Run {
11298            run_description: "Start of word matches completion text",
11299            initial_state: "before ediˇ after".into(),
11300            buffer_marked_text: "before <edi|> after".into(),
11301            completion_label: "editor",
11302            completion_text: "editor",
11303            expected_with_insert_mode: "before editorˇ after".into(),
11304            expected_with_replace_mode: "before editorˇ after".into(),
11305            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11306            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11307        },
11308        Run {
11309            run_description: "Accept same text at the middle of the word",
11310            initial_state: "before ediˇtor after".into(),
11311            buffer_marked_text: "before <edi|tor> after".into(),
11312            completion_label: "editor",
11313            completion_text: "editor",
11314            expected_with_insert_mode: "before editorˇtor after".into(),
11315            expected_with_replace_mode: "before editorˇ after".into(),
11316            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11317            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11318        },
11319        Run {
11320            run_description: "End of word matches completion text -- cursor at end",
11321            initial_state: "before torˇ after".into(),
11322            buffer_marked_text: "before <tor|> after".into(),
11323            completion_label: "editor",
11324            completion_text: "editor",
11325            expected_with_insert_mode: "before editorˇ after".into(),
11326            expected_with_replace_mode: "before editorˇ after".into(),
11327            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11328            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11329        },
11330        Run {
11331            run_description: "End of word matches completion text -- cursor at start",
11332            initial_state: "before ˇtor after".into(),
11333            buffer_marked_text: "before <|tor> after".into(),
11334            completion_label: "editor",
11335            completion_text: "editor",
11336            expected_with_insert_mode: "before editorˇtor after".into(),
11337            expected_with_replace_mode: "before editorˇ after".into(),
11338            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11339            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11340        },
11341        Run {
11342            run_description: "Prepend text containing whitespace",
11343            initial_state: "pˇfield: bool".into(),
11344            buffer_marked_text: "<p|field>: bool".into(),
11345            completion_label: "pub ",
11346            completion_text: "pub ",
11347            expected_with_insert_mode: "pub ˇfield: bool".into(),
11348            expected_with_replace_mode: "pub ˇ: bool".into(),
11349            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11350            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11351        },
11352        Run {
11353            run_description: "Add element to start of list",
11354            initial_state: "[element_ˇelement_2]".into(),
11355            buffer_marked_text: "[<element_|element_2>]".into(),
11356            completion_label: "element_1",
11357            completion_text: "element_1",
11358            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11359            expected_with_replace_mode: "[element_1ˇ]".into(),
11360            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11361            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11362        },
11363        Run {
11364            run_description: "Add element to start of list -- first and second elements are equal",
11365            initial_state: "[elˇelement]".into(),
11366            buffer_marked_text: "[<el|element>]".into(),
11367            completion_label: "element",
11368            completion_text: "element",
11369            expected_with_insert_mode: "[elementˇelement]".into(),
11370            expected_with_replace_mode: "[elementˇ]".into(),
11371            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11372            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11373        },
11374        Run {
11375            run_description: "Ends with matching suffix",
11376            initial_state: "SubˇError".into(),
11377            buffer_marked_text: "<Sub|Error>".into(),
11378            completion_label: "SubscriptionError",
11379            completion_text: "SubscriptionError",
11380            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11381            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11382            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11383            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11384        },
11385        Run {
11386            run_description: "Suffix is a subsequence -- contiguous",
11387            initial_state: "SubˇErr".into(),
11388            buffer_marked_text: "<Sub|Err>".into(),
11389            completion_label: "SubscriptionError",
11390            completion_text: "SubscriptionError",
11391            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11392            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11393            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11394            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11395        },
11396        Run {
11397            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11398            initial_state: "Suˇscrirr".into(),
11399            buffer_marked_text: "<Su|scrirr>".into(),
11400            completion_label: "SubscriptionError",
11401            completion_text: "SubscriptionError",
11402            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11403            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11404            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11405            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11406        },
11407        Run {
11408            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11409            initial_state: "foo(indˇix)".into(),
11410            buffer_marked_text: "foo(<ind|ix>)".into(),
11411            completion_label: "node_index",
11412            completion_text: "node_index",
11413            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11414            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11415            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11416            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11417        },
11418        Run {
11419            run_description: "Replace range ends before cursor - should extend to cursor",
11420            initial_state: "before editˇo after".into(),
11421            buffer_marked_text: "before <{ed}>it|o after".into(),
11422            completion_label: "editor",
11423            completion_text: "editor",
11424            expected_with_insert_mode: "before editorˇo after".into(),
11425            expected_with_replace_mode: "before editorˇo after".into(),
11426            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11427            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11428        },
11429        Run {
11430            run_description: "Uses label for suffix matching",
11431            initial_state: "before ediˇtor after".into(),
11432            buffer_marked_text: "before <edi|tor> after".into(),
11433            completion_label: "editor",
11434            completion_text: "editor()",
11435            expected_with_insert_mode: "before editor()ˇtor after".into(),
11436            expected_with_replace_mode: "before editor()ˇ after".into(),
11437            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11438            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11439        },
11440        Run {
11441            run_description: "Case insensitive subsequence and suffix matching",
11442            initial_state: "before EDiˇtoR after".into(),
11443            buffer_marked_text: "before <EDi|toR> after".into(),
11444            completion_label: "editor",
11445            completion_text: "editor",
11446            expected_with_insert_mode: "before editorˇtoR after".into(),
11447            expected_with_replace_mode: "before editorˇ after".into(),
11448            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11449            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11450        },
11451    ];
11452
11453    for run in runs {
11454        let run_variations = [
11455            (LspInsertMode::Insert, run.expected_with_insert_mode),
11456            (LspInsertMode::Replace, run.expected_with_replace_mode),
11457            (
11458                LspInsertMode::ReplaceSubsequence,
11459                run.expected_with_replace_subsequence_mode,
11460            ),
11461            (
11462                LspInsertMode::ReplaceSuffix,
11463                run.expected_with_replace_suffix_mode,
11464            ),
11465        ];
11466
11467        for (lsp_insert_mode, expected_text) in run_variations {
11468            eprintln!(
11469                "run = {:?}, mode = {lsp_insert_mode:.?}",
11470                run.run_description,
11471            );
11472
11473            update_test_language_settings(&mut cx, |settings| {
11474                settings.defaults.completions = Some(CompletionSettings {
11475                    lsp_insert_mode,
11476                    words: WordsCompletionMode::Disabled,
11477                    lsp: true,
11478                    lsp_fetch_timeout_ms: 0,
11479                });
11480            });
11481
11482            cx.set_state(&run.initial_state);
11483            cx.update_editor(|editor, window, cx| {
11484                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11485            });
11486
11487            let counter = Arc::new(AtomicUsize::new(0));
11488            handle_completion_request_with_insert_and_replace(
11489                &mut cx,
11490                &run.buffer_marked_text,
11491                vec![(run.completion_label, run.completion_text)],
11492                counter.clone(),
11493            )
11494            .await;
11495            cx.condition(|editor, _| editor.context_menu_visible())
11496                .await;
11497            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11498
11499            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11500                editor
11501                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11502                    .unwrap()
11503            });
11504            cx.assert_editor_state(&expected_text);
11505            handle_resolve_completion_request(&mut cx, None).await;
11506            apply_additional_edits.await.unwrap();
11507        }
11508    }
11509}
11510
11511#[gpui::test]
11512async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11513    init_test(cx, |_| {});
11514    let mut cx = EditorLspTestContext::new_rust(
11515        lsp::ServerCapabilities {
11516            completion_provider: Some(lsp::CompletionOptions {
11517                resolve_provider: Some(true),
11518                ..Default::default()
11519            }),
11520            ..Default::default()
11521        },
11522        cx,
11523    )
11524    .await;
11525
11526    let initial_state = "SubˇError";
11527    let buffer_marked_text = "<Sub|Error>";
11528    let completion_text = "SubscriptionError";
11529    let expected_with_insert_mode = "SubscriptionErrorˇError";
11530    let expected_with_replace_mode = "SubscriptionErrorˇ";
11531
11532    update_test_language_settings(&mut cx, |settings| {
11533        settings.defaults.completions = Some(CompletionSettings {
11534            words: WordsCompletionMode::Disabled,
11535            // set the opposite here to ensure that the action is overriding the default behavior
11536            lsp_insert_mode: LspInsertMode::Insert,
11537            lsp: true,
11538            lsp_fetch_timeout_ms: 0,
11539        });
11540    });
11541
11542    cx.set_state(initial_state);
11543    cx.update_editor(|editor, window, cx| {
11544        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11545    });
11546
11547    let counter = Arc::new(AtomicUsize::new(0));
11548    handle_completion_request_with_insert_and_replace(
11549        &mut cx,
11550        &buffer_marked_text,
11551        vec![(completion_text, completion_text)],
11552        counter.clone(),
11553    )
11554    .await;
11555    cx.condition(|editor, _| editor.context_menu_visible())
11556        .await;
11557    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11558
11559    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11560        editor
11561            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11562            .unwrap()
11563    });
11564    cx.assert_editor_state(&expected_with_replace_mode);
11565    handle_resolve_completion_request(&mut cx, None).await;
11566    apply_additional_edits.await.unwrap();
11567
11568    update_test_language_settings(&mut cx, |settings| {
11569        settings.defaults.completions = Some(CompletionSettings {
11570            words: WordsCompletionMode::Disabled,
11571            // set the opposite here to ensure that the action is overriding the default behavior
11572            lsp_insert_mode: LspInsertMode::Replace,
11573            lsp: true,
11574            lsp_fetch_timeout_ms: 0,
11575        });
11576    });
11577
11578    cx.set_state(initial_state);
11579    cx.update_editor(|editor, window, cx| {
11580        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11581    });
11582    handle_completion_request_with_insert_and_replace(
11583        &mut cx,
11584        &buffer_marked_text,
11585        vec![(completion_text, completion_text)],
11586        counter.clone(),
11587    )
11588    .await;
11589    cx.condition(|editor, _| editor.context_menu_visible())
11590        .await;
11591    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11592
11593    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11594        editor
11595            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11596            .unwrap()
11597    });
11598    cx.assert_editor_state(&expected_with_insert_mode);
11599    handle_resolve_completion_request(&mut cx, None).await;
11600    apply_additional_edits.await.unwrap();
11601}
11602
11603#[gpui::test]
11604async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11605    init_test(cx, |_| {});
11606    let mut cx = EditorLspTestContext::new_rust(
11607        lsp::ServerCapabilities {
11608            completion_provider: Some(lsp::CompletionOptions {
11609                resolve_provider: Some(true),
11610                ..Default::default()
11611            }),
11612            ..Default::default()
11613        },
11614        cx,
11615    )
11616    .await;
11617
11618    // scenario: surrounding text matches completion text
11619    let completion_text = "to_offset";
11620    let initial_state = indoc! {"
11621        1. buf.to_offˇsuffix
11622        2. buf.to_offˇsuf
11623        3. buf.to_offˇfix
11624        4. buf.to_offˇ
11625        5. into_offˇensive
11626        6. ˇsuffix
11627        7. let ˇ //
11628        8. aaˇzz
11629        9. buf.to_off«zzzzzˇ»suffix
11630        10. buf.«ˇzzzzz»suffix
11631        11. to_off«ˇzzzzz»
11632
11633        buf.to_offˇsuffix  // newest cursor
11634    "};
11635    let completion_marked_buffer = indoc! {"
11636        1. buf.to_offsuffix
11637        2. buf.to_offsuf
11638        3. buf.to_offfix
11639        4. buf.to_off
11640        5. into_offensive
11641        6. suffix
11642        7. let  //
11643        8. aazz
11644        9. buf.to_offzzzzzsuffix
11645        10. buf.zzzzzsuffix
11646        11. to_offzzzzz
11647
11648        buf.<to_off|suffix>  // newest cursor
11649    "};
11650    let expected = indoc! {"
11651        1. buf.to_offsetˇ
11652        2. buf.to_offsetˇsuf
11653        3. buf.to_offsetˇfix
11654        4. buf.to_offsetˇ
11655        5. into_offsetˇensive
11656        6. to_offsetˇsuffix
11657        7. let to_offsetˇ //
11658        8. aato_offsetˇzz
11659        9. buf.to_offsetˇ
11660        10. buf.to_offsetˇsuffix
11661        11. to_offsetˇ
11662
11663        buf.to_offsetˇ  // newest cursor
11664    "};
11665    cx.set_state(initial_state);
11666    cx.update_editor(|editor, window, cx| {
11667        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11668    });
11669    handle_completion_request_with_insert_and_replace(
11670        &mut cx,
11671        completion_marked_buffer,
11672        vec![(completion_text, completion_text)],
11673        Arc::new(AtomicUsize::new(0)),
11674    )
11675    .await;
11676    cx.condition(|editor, _| editor.context_menu_visible())
11677        .await;
11678    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11679        editor
11680            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11681            .unwrap()
11682    });
11683    cx.assert_editor_state(expected);
11684    handle_resolve_completion_request(&mut cx, None).await;
11685    apply_additional_edits.await.unwrap();
11686
11687    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11688    let completion_text = "foo_and_bar";
11689    let initial_state = indoc! {"
11690        1. ooanbˇ
11691        2. zooanbˇ
11692        3. ooanbˇz
11693        4. zooanbˇz
11694        5. ooanˇ
11695        6. oanbˇ
11696
11697        ooanbˇ
11698    "};
11699    let completion_marked_buffer = indoc! {"
11700        1. ooanb
11701        2. zooanb
11702        3. ooanbz
11703        4. zooanbz
11704        5. ooan
11705        6. oanb
11706
11707        <ooanb|>
11708    "};
11709    let expected = indoc! {"
11710        1. foo_and_barˇ
11711        2. zfoo_and_barˇ
11712        3. foo_and_barˇz
11713        4. zfoo_and_barˇz
11714        5. ooanfoo_and_barˇ
11715        6. oanbfoo_and_barˇ
11716
11717        foo_and_barˇ
11718    "};
11719    cx.set_state(initial_state);
11720    cx.update_editor(|editor, window, cx| {
11721        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11722    });
11723    handle_completion_request_with_insert_and_replace(
11724        &mut cx,
11725        completion_marked_buffer,
11726        vec![(completion_text, completion_text)],
11727        Arc::new(AtomicUsize::new(0)),
11728    )
11729    .await;
11730    cx.condition(|editor, _| editor.context_menu_visible())
11731        .await;
11732    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11733        editor
11734            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11735            .unwrap()
11736    });
11737    cx.assert_editor_state(expected);
11738    handle_resolve_completion_request(&mut cx, None).await;
11739    apply_additional_edits.await.unwrap();
11740
11741    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11742    // (expects the same as if it was inserted at the end)
11743    let completion_text = "foo_and_bar";
11744    let initial_state = indoc! {"
11745        1. ooˇanb
11746        2. zooˇanb
11747        3. ooˇanbz
11748        4. zooˇanbz
11749
11750        ooˇanb
11751    "};
11752    let completion_marked_buffer = indoc! {"
11753        1. ooanb
11754        2. zooanb
11755        3. ooanbz
11756        4. zooanbz
11757
11758        <oo|anb>
11759    "};
11760    let expected = indoc! {"
11761        1. foo_and_barˇ
11762        2. zfoo_and_barˇ
11763        3. foo_and_barˇz
11764        4. zfoo_and_barˇz
11765
11766        foo_and_barˇ
11767    "};
11768    cx.set_state(initial_state);
11769    cx.update_editor(|editor, window, cx| {
11770        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11771    });
11772    handle_completion_request_with_insert_and_replace(
11773        &mut cx,
11774        completion_marked_buffer,
11775        vec![(completion_text, completion_text)],
11776        Arc::new(AtomicUsize::new(0)),
11777    )
11778    .await;
11779    cx.condition(|editor, _| editor.context_menu_visible())
11780        .await;
11781    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11782        editor
11783            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11784            .unwrap()
11785    });
11786    cx.assert_editor_state(expected);
11787    handle_resolve_completion_request(&mut cx, None).await;
11788    apply_additional_edits.await.unwrap();
11789}
11790
11791// This used to crash
11792#[gpui::test]
11793async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11794    init_test(cx, |_| {});
11795
11796    let buffer_text = indoc! {"
11797        fn main() {
11798            10.satu;
11799
11800            //
11801            // separate cursors so they open in different excerpts (manually reproducible)
11802            //
11803
11804            10.satu20;
11805        }
11806    "};
11807    let multibuffer_text_with_selections = indoc! {"
11808        fn main() {
11809            10.satuˇ;
11810
11811            //
11812
11813            //
11814
11815            10.satuˇ20;
11816        }
11817    "};
11818    let expected_multibuffer = indoc! {"
11819        fn main() {
11820            10.saturating_sub()ˇ;
11821
11822            //
11823
11824            //
11825
11826            10.saturating_sub()ˇ;
11827        }
11828    "};
11829
11830    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11831    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11832
11833    let fs = FakeFs::new(cx.executor());
11834    fs.insert_tree(
11835        path!("/a"),
11836        json!({
11837            "main.rs": buffer_text,
11838        }),
11839    )
11840    .await;
11841
11842    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11844    language_registry.add(rust_lang());
11845    let mut fake_servers = language_registry.register_fake_lsp(
11846        "Rust",
11847        FakeLspAdapter {
11848            capabilities: lsp::ServerCapabilities {
11849                completion_provider: Some(lsp::CompletionOptions {
11850                    resolve_provider: None,
11851                    ..lsp::CompletionOptions::default()
11852                }),
11853                ..lsp::ServerCapabilities::default()
11854            },
11855            ..FakeLspAdapter::default()
11856        },
11857    );
11858    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11859    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11860    let buffer = project
11861        .update(cx, |project, cx| {
11862            project.open_local_buffer(path!("/a/main.rs"), cx)
11863        })
11864        .await
11865        .unwrap();
11866
11867    let multi_buffer = cx.new(|cx| {
11868        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11869        multi_buffer.push_excerpts(
11870            buffer.clone(),
11871            [ExcerptRange::new(0..first_excerpt_end)],
11872            cx,
11873        );
11874        multi_buffer.push_excerpts(
11875            buffer.clone(),
11876            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11877            cx,
11878        );
11879        multi_buffer
11880    });
11881
11882    let editor = workspace
11883        .update(cx, |_, window, cx| {
11884            cx.new(|cx| {
11885                Editor::new(
11886                    EditorMode::Full {
11887                        scale_ui_elements_with_buffer_font_size: false,
11888                        show_active_line_background: false,
11889                        sized_by_content: false,
11890                    },
11891                    multi_buffer.clone(),
11892                    Some(project.clone()),
11893                    window,
11894                    cx,
11895                )
11896            })
11897        })
11898        .unwrap();
11899
11900    let pane = workspace
11901        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11902        .unwrap();
11903    pane.update_in(cx, |pane, window, cx| {
11904        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11905    });
11906
11907    let fake_server = fake_servers.next().await.unwrap();
11908
11909    editor.update_in(cx, |editor, window, cx| {
11910        editor.change_selections(None, window, cx, |s| {
11911            s.select_ranges([
11912                Point::new(1, 11)..Point::new(1, 11),
11913                Point::new(7, 11)..Point::new(7, 11),
11914            ])
11915        });
11916
11917        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11918    });
11919
11920    editor.update_in(cx, |editor, window, cx| {
11921        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11922    });
11923
11924    fake_server
11925        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11926            let completion_item = lsp::CompletionItem {
11927                label: "saturating_sub()".into(),
11928                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11929                    lsp::InsertReplaceEdit {
11930                        new_text: "saturating_sub()".to_owned(),
11931                        insert: lsp::Range::new(
11932                            lsp::Position::new(7, 7),
11933                            lsp::Position::new(7, 11),
11934                        ),
11935                        replace: lsp::Range::new(
11936                            lsp::Position::new(7, 7),
11937                            lsp::Position::new(7, 13),
11938                        ),
11939                    },
11940                )),
11941                ..lsp::CompletionItem::default()
11942            };
11943
11944            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11945        })
11946        .next()
11947        .await
11948        .unwrap();
11949
11950    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11951        .await;
11952
11953    editor
11954        .update_in(cx, |editor, window, cx| {
11955            editor
11956                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11957                .unwrap()
11958        })
11959        .await
11960        .unwrap();
11961
11962    editor.update(cx, |editor, cx| {
11963        assert_text_with_selections(editor, expected_multibuffer, cx);
11964    })
11965}
11966
11967#[gpui::test]
11968async fn test_completion(cx: &mut TestAppContext) {
11969    init_test(cx, |_| {});
11970
11971    let mut cx = EditorLspTestContext::new_rust(
11972        lsp::ServerCapabilities {
11973            completion_provider: Some(lsp::CompletionOptions {
11974                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11975                resolve_provider: Some(true),
11976                ..Default::default()
11977            }),
11978            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11979            ..Default::default()
11980        },
11981        cx,
11982    )
11983    .await;
11984    let counter = Arc::new(AtomicUsize::new(0));
11985
11986    cx.set_state(indoc! {"
11987        oneˇ
11988        two
11989        three
11990    "});
11991    cx.simulate_keystroke(".");
11992    handle_completion_request(
11993        indoc! {"
11994            one.|<>
11995            two
11996            three
11997        "},
11998        vec!["first_completion", "second_completion"],
11999        true,
12000        counter.clone(),
12001        &mut cx,
12002    )
12003    .await;
12004    cx.condition(|editor, _| editor.context_menu_visible())
12005        .await;
12006    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12007
12008    let _handler = handle_signature_help_request(
12009        &mut cx,
12010        lsp::SignatureHelp {
12011            signatures: vec![lsp::SignatureInformation {
12012                label: "test signature".to_string(),
12013                documentation: None,
12014                parameters: Some(vec![lsp::ParameterInformation {
12015                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12016                    documentation: None,
12017                }]),
12018                active_parameter: None,
12019            }],
12020            active_signature: None,
12021            active_parameter: None,
12022        },
12023    );
12024    cx.update_editor(|editor, window, cx| {
12025        assert!(
12026            !editor.signature_help_state.is_shown(),
12027            "No signature help was called for"
12028        );
12029        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12030    });
12031    cx.run_until_parked();
12032    cx.update_editor(|editor, _, _| {
12033        assert!(
12034            !editor.signature_help_state.is_shown(),
12035            "No signature help should be shown when completions menu is open"
12036        );
12037    });
12038
12039    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12040        editor.context_menu_next(&Default::default(), window, cx);
12041        editor
12042            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12043            .unwrap()
12044    });
12045    cx.assert_editor_state(indoc! {"
12046        one.second_completionˇ
12047        two
12048        three
12049    "});
12050
12051    handle_resolve_completion_request(
12052        &mut cx,
12053        Some(vec![
12054            (
12055                //This overlaps with the primary completion edit which is
12056                //misbehavior from the LSP spec, test that we filter it out
12057                indoc! {"
12058                    one.second_ˇcompletion
12059                    two
12060                    threeˇ
12061                "},
12062                "overlapping additional edit",
12063            ),
12064            (
12065                indoc! {"
12066                    one.second_completion
12067                    two
12068                    threeˇ
12069                "},
12070                "\nadditional edit",
12071            ),
12072        ]),
12073    )
12074    .await;
12075    apply_additional_edits.await.unwrap();
12076    cx.assert_editor_state(indoc! {"
12077        one.second_completionˇ
12078        two
12079        three
12080        additional edit
12081    "});
12082
12083    cx.set_state(indoc! {"
12084        one.second_completion
12085        twoˇ
12086        threeˇ
12087        additional edit
12088    "});
12089    cx.simulate_keystroke(" ");
12090    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12091    cx.simulate_keystroke("s");
12092    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12093
12094    cx.assert_editor_state(indoc! {"
12095        one.second_completion
12096        two sˇ
12097        three sˇ
12098        additional edit
12099    "});
12100    handle_completion_request(
12101        indoc! {"
12102            one.second_completion
12103            two s
12104            three <s|>
12105            additional edit
12106        "},
12107        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12108        true,
12109        counter.clone(),
12110        &mut cx,
12111    )
12112    .await;
12113    cx.condition(|editor, _| editor.context_menu_visible())
12114        .await;
12115    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12116
12117    cx.simulate_keystroke("i");
12118
12119    handle_completion_request(
12120        indoc! {"
12121            one.second_completion
12122            two si
12123            three <si|>
12124            additional edit
12125        "},
12126        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12127        true,
12128        counter.clone(),
12129        &mut cx,
12130    )
12131    .await;
12132    cx.condition(|editor, _| editor.context_menu_visible())
12133        .await;
12134    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12135
12136    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12137        editor
12138            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12139            .unwrap()
12140    });
12141    cx.assert_editor_state(indoc! {"
12142        one.second_completion
12143        two sixth_completionˇ
12144        three sixth_completionˇ
12145        additional edit
12146    "});
12147
12148    apply_additional_edits.await.unwrap();
12149
12150    update_test_language_settings(&mut cx, |settings| {
12151        settings.defaults.show_completions_on_input = Some(false);
12152    });
12153    cx.set_state("editorˇ");
12154    cx.simulate_keystroke(".");
12155    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12156    cx.simulate_keystrokes("c l o");
12157    cx.assert_editor_state("editor.cloˇ");
12158    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12159    cx.update_editor(|editor, window, cx| {
12160        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12161    });
12162    handle_completion_request(
12163        "editor.<clo|>",
12164        vec!["close", "clobber"],
12165        true,
12166        counter.clone(),
12167        &mut cx,
12168    )
12169    .await;
12170    cx.condition(|editor, _| editor.context_menu_visible())
12171        .await;
12172    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12173
12174    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12175        editor
12176            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12177            .unwrap()
12178    });
12179    cx.assert_editor_state("editor.clobberˇ");
12180    handle_resolve_completion_request(&mut cx, None).await;
12181    apply_additional_edits.await.unwrap();
12182}
12183
12184#[gpui::test]
12185async fn test_completion_reuse(cx: &mut TestAppContext) {
12186    init_test(cx, |_| {});
12187
12188    let mut cx = EditorLspTestContext::new_rust(
12189        lsp::ServerCapabilities {
12190            completion_provider: Some(lsp::CompletionOptions {
12191                trigger_characters: Some(vec![".".to_string()]),
12192                ..Default::default()
12193            }),
12194            ..Default::default()
12195        },
12196        cx,
12197    )
12198    .await;
12199
12200    let counter = Arc::new(AtomicUsize::new(0));
12201    cx.set_state("objˇ");
12202    cx.simulate_keystroke(".");
12203
12204    // Initial completion request returns complete results
12205    let is_incomplete = false;
12206    handle_completion_request(
12207        "obj.|<>",
12208        vec!["a", "ab", "abc"],
12209        is_incomplete,
12210        counter.clone(),
12211        &mut cx,
12212    )
12213    .await;
12214    cx.run_until_parked();
12215    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12216    cx.assert_editor_state("obj.ˇ");
12217    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12218
12219    // Type "a" - filters existing completions
12220    cx.simulate_keystroke("a");
12221    cx.run_until_parked();
12222    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12223    cx.assert_editor_state("obj.aˇ");
12224    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12225
12226    // Type "b" - filters existing completions
12227    cx.simulate_keystroke("b");
12228    cx.run_until_parked();
12229    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12230    cx.assert_editor_state("obj.abˇ");
12231    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12232
12233    // Type "c" - filters existing completions
12234    cx.simulate_keystroke("c");
12235    cx.run_until_parked();
12236    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12237    cx.assert_editor_state("obj.abcˇ");
12238    check_displayed_completions(vec!["abc"], &mut cx);
12239
12240    // Backspace to delete "c" - filters existing completions
12241    cx.update_editor(|editor, window, cx| {
12242        editor.backspace(&Backspace, window, cx);
12243    });
12244    cx.run_until_parked();
12245    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12246    cx.assert_editor_state("obj.abˇ");
12247    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12248
12249    // Moving cursor to the left dismisses menu.
12250    cx.update_editor(|editor, window, cx| {
12251        editor.move_left(&MoveLeft, window, cx);
12252    });
12253    cx.run_until_parked();
12254    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12255    cx.assert_editor_state("obj.aˇb");
12256    cx.update_editor(|editor, _, _| {
12257        assert_eq!(editor.context_menu_visible(), false);
12258    });
12259
12260    // Type "b" - new request
12261    cx.simulate_keystroke("b");
12262    let is_incomplete = false;
12263    handle_completion_request(
12264        "obj.<ab|>a",
12265        vec!["ab", "abc"],
12266        is_incomplete,
12267        counter.clone(),
12268        &mut cx,
12269    )
12270    .await;
12271    cx.run_until_parked();
12272    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12273    cx.assert_editor_state("obj.abˇb");
12274    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12275
12276    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12277    cx.update_editor(|editor, window, cx| {
12278        editor.backspace(&Backspace, window, cx);
12279    });
12280    let is_incomplete = false;
12281    handle_completion_request(
12282        "obj.<a|>b",
12283        vec!["a", "ab", "abc"],
12284        is_incomplete,
12285        counter.clone(),
12286        &mut cx,
12287    )
12288    .await;
12289    cx.run_until_parked();
12290    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12291    cx.assert_editor_state("obj.aˇb");
12292    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12293
12294    // Backspace to delete "a" - dismisses menu.
12295    cx.update_editor(|editor, window, cx| {
12296        editor.backspace(&Backspace, window, cx);
12297    });
12298    cx.run_until_parked();
12299    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12300    cx.assert_editor_state("obj.ˇb");
12301    cx.update_editor(|editor, _, _| {
12302        assert_eq!(editor.context_menu_visible(), false);
12303    });
12304}
12305
12306#[gpui::test]
12307async fn test_word_completion(cx: &mut TestAppContext) {
12308    let lsp_fetch_timeout_ms = 10;
12309    init_test(cx, |language_settings| {
12310        language_settings.defaults.completions = Some(CompletionSettings {
12311            words: WordsCompletionMode::Fallback,
12312            lsp: true,
12313            lsp_fetch_timeout_ms: 10,
12314            lsp_insert_mode: LspInsertMode::Insert,
12315        });
12316    });
12317
12318    let mut cx = EditorLspTestContext::new_rust(
12319        lsp::ServerCapabilities {
12320            completion_provider: Some(lsp::CompletionOptions {
12321                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12322                ..lsp::CompletionOptions::default()
12323            }),
12324            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12325            ..lsp::ServerCapabilities::default()
12326        },
12327        cx,
12328    )
12329    .await;
12330
12331    let throttle_completions = Arc::new(AtomicBool::new(false));
12332
12333    let lsp_throttle_completions = throttle_completions.clone();
12334    let _completion_requests_handler =
12335        cx.lsp
12336            .server
12337            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12338                let lsp_throttle_completions = lsp_throttle_completions.clone();
12339                let cx = cx.clone();
12340                async move {
12341                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12342                        cx.background_executor()
12343                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12344                            .await;
12345                    }
12346                    Ok(Some(lsp::CompletionResponse::Array(vec![
12347                        lsp::CompletionItem {
12348                            label: "first".into(),
12349                            ..lsp::CompletionItem::default()
12350                        },
12351                        lsp::CompletionItem {
12352                            label: "last".into(),
12353                            ..lsp::CompletionItem::default()
12354                        },
12355                    ])))
12356                }
12357            });
12358
12359    cx.set_state(indoc! {"
12360        oneˇ
12361        two
12362        three
12363    "});
12364    cx.simulate_keystroke(".");
12365    cx.executor().run_until_parked();
12366    cx.condition(|editor, _| editor.context_menu_visible())
12367        .await;
12368    cx.update_editor(|editor, window, cx| {
12369        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12370        {
12371            assert_eq!(
12372                completion_menu_entries(&menu),
12373                &["first", "last"],
12374                "When LSP server is fast to reply, no fallback word completions are used"
12375            );
12376        } else {
12377            panic!("expected completion menu to be open");
12378        }
12379        editor.cancel(&Cancel, window, cx);
12380    });
12381    cx.executor().run_until_parked();
12382    cx.condition(|editor, _| !editor.context_menu_visible())
12383        .await;
12384
12385    throttle_completions.store(true, atomic::Ordering::Release);
12386    cx.simulate_keystroke(".");
12387    cx.executor()
12388        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12389    cx.executor().run_until_parked();
12390    cx.condition(|editor, _| editor.context_menu_visible())
12391        .await;
12392    cx.update_editor(|editor, _, _| {
12393        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12394        {
12395            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12396                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12397        } else {
12398            panic!("expected completion menu to be open");
12399        }
12400    });
12401}
12402
12403#[gpui::test]
12404async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12405    init_test(cx, |language_settings| {
12406        language_settings.defaults.completions = Some(CompletionSettings {
12407            words: WordsCompletionMode::Enabled,
12408            lsp: true,
12409            lsp_fetch_timeout_ms: 0,
12410            lsp_insert_mode: LspInsertMode::Insert,
12411        });
12412    });
12413
12414    let mut cx = EditorLspTestContext::new_rust(
12415        lsp::ServerCapabilities {
12416            completion_provider: Some(lsp::CompletionOptions {
12417                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12418                ..lsp::CompletionOptions::default()
12419            }),
12420            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12421            ..lsp::ServerCapabilities::default()
12422        },
12423        cx,
12424    )
12425    .await;
12426
12427    let _completion_requests_handler =
12428        cx.lsp
12429            .server
12430            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12431                Ok(Some(lsp::CompletionResponse::Array(vec![
12432                    lsp::CompletionItem {
12433                        label: "first".into(),
12434                        ..lsp::CompletionItem::default()
12435                    },
12436                    lsp::CompletionItem {
12437                        label: "last".into(),
12438                        ..lsp::CompletionItem::default()
12439                    },
12440                ])))
12441            });
12442
12443    cx.set_state(indoc! {"ˇ
12444        first
12445        last
12446        second
12447    "});
12448    cx.simulate_keystroke(".");
12449    cx.executor().run_until_parked();
12450    cx.condition(|editor, _| editor.context_menu_visible())
12451        .await;
12452    cx.update_editor(|editor, _, _| {
12453        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12454        {
12455            assert_eq!(
12456                completion_menu_entries(&menu),
12457                &["first", "last", "second"],
12458                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12459            );
12460        } else {
12461            panic!("expected completion menu to be open");
12462        }
12463    });
12464}
12465
12466#[gpui::test]
12467async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12468    init_test(cx, |language_settings| {
12469        language_settings.defaults.completions = Some(CompletionSettings {
12470            words: WordsCompletionMode::Disabled,
12471            lsp: true,
12472            lsp_fetch_timeout_ms: 0,
12473            lsp_insert_mode: LspInsertMode::Insert,
12474        });
12475    });
12476
12477    let mut cx = EditorLspTestContext::new_rust(
12478        lsp::ServerCapabilities {
12479            completion_provider: Some(lsp::CompletionOptions {
12480                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12481                ..lsp::CompletionOptions::default()
12482            }),
12483            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12484            ..lsp::ServerCapabilities::default()
12485        },
12486        cx,
12487    )
12488    .await;
12489
12490    let _completion_requests_handler =
12491        cx.lsp
12492            .server
12493            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12494                panic!("LSP completions should not be queried when dealing with word completions")
12495            });
12496
12497    cx.set_state(indoc! {"ˇ
12498        first
12499        last
12500        second
12501    "});
12502    cx.update_editor(|editor, window, cx| {
12503        editor.show_word_completions(&ShowWordCompletions, window, cx);
12504    });
12505    cx.executor().run_until_parked();
12506    cx.condition(|editor, _| editor.context_menu_visible())
12507        .await;
12508    cx.update_editor(|editor, _, _| {
12509        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12510        {
12511            assert_eq!(
12512                completion_menu_entries(&menu),
12513                &["first", "last", "second"],
12514                "`ShowWordCompletions` action should show word completions"
12515            );
12516        } else {
12517            panic!("expected completion menu to be open");
12518        }
12519    });
12520
12521    cx.simulate_keystroke("l");
12522    cx.executor().run_until_parked();
12523    cx.condition(|editor, _| editor.context_menu_visible())
12524        .await;
12525    cx.update_editor(|editor, _, _| {
12526        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12527        {
12528            assert_eq!(
12529                completion_menu_entries(&menu),
12530                &["last"],
12531                "After showing word completions, further editing should filter them and not query the LSP"
12532            );
12533        } else {
12534            panic!("expected completion menu to be open");
12535        }
12536    });
12537}
12538
12539#[gpui::test]
12540async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12541    init_test(cx, |language_settings| {
12542        language_settings.defaults.completions = Some(CompletionSettings {
12543            words: WordsCompletionMode::Fallback,
12544            lsp: false,
12545            lsp_fetch_timeout_ms: 0,
12546            lsp_insert_mode: LspInsertMode::Insert,
12547        });
12548    });
12549
12550    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12551
12552    cx.set_state(indoc! {"ˇ
12553        0_usize
12554        let
12555        33
12556        4.5f32
12557    "});
12558    cx.update_editor(|editor, window, cx| {
12559        editor.show_completions(&ShowCompletions::default(), window, cx);
12560    });
12561    cx.executor().run_until_parked();
12562    cx.condition(|editor, _| editor.context_menu_visible())
12563        .await;
12564    cx.update_editor(|editor, window, cx| {
12565        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12566        {
12567            assert_eq!(
12568                completion_menu_entries(&menu),
12569                &["let"],
12570                "With no digits in the completion query, no digits should be in the word completions"
12571            );
12572        } else {
12573            panic!("expected completion menu to be open");
12574        }
12575        editor.cancel(&Cancel, window, cx);
12576    });
12577
12578    cx.set_state(indoc! {"12579        0_usize
12580        let
12581        3
12582        33.35f32
12583    "});
12584    cx.update_editor(|editor, window, cx| {
12585        editor.show_completions(&ShowCompletions::default(), window, cx);
12586    });
12587    cx.executor().run_until_parked();
12588    cx.condition(|editor, _| editor.context_menu_visible())
12589        .await;
12590    cx.update_editor(|editor, _, _| {
12591        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12592        {
12593            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12594                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12595        } else {
12596            panic!("expected completion menu to be open");
12597        }
12598    });
12599}
12600
12601fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12602    let position = || lsp::Position {
12603        line: params.text_document_position.position.line,
12604        character: params.text_document_position.position.character,
12605    };
12606    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12607        range: lsp::Range {
12608            start: position(),
12609            end: position(),
12610        },
12611        new_text: text.to_string(),
12612    }))
12613}
12614
12615#[gpui::test]
12616async fn test_multiline_completion(cx: &mut TestAppContext) {
12617    init_test(cx, |_| {});
12618
12619    let fs = FakeFs::new(cx.executor());
12620    fs.insert_tree(
12621        path!("/a"),
12622        json!({
12623            "main.ts": "a",
12624        }),
12625    )
12626    .await;
12627
12628    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12629    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12630    let typescript_language = Arc::new(Language::new(
12631        LanguageConfig {
12632            name: "TypeScript".into(),
12633            matcher: LanguageMatcher {
12634                path_suffixes: vec!["ts".to_string()],
12635                ..LanguageMatcher::default()
12636            },
12637            line_comments: vec!["// ".into()],
12638            ..LanguageConfig::default()
12639        },
12640        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12641    ));
12642    language_registry.add(typescript_language.clone());
12643    let mut fake_servers = language_registry.register_fake_lsp(
12644        "TypeScript",
12645        FakeLspAdapter {
12646            capabilities: lsp::ServerCapabilities {
12647                completion_provider: Some(lsp::CompletionOptions {
12648                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12649                    ..lsp::CompletionOptions::default()
12650                }),
12651                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12652                ..lsp::ServerCapabilities::default()
12653            },
12654            // Emulate vtsls label generation
12655            label_for_completion: Some(Box::new(|item, _| {
12656                let text = if let Some(description) = item
12657                    .label_details
12658                    .as_ref()
12659                    .and_then(|label_details| label_details.description.as_ref())
12660                {
12661                    format!("{} {}", item.label, description)
12662                } else if let Some(detail) = &item.detail {
12663                    format!("{} {}", item.label, detail)
12664                } else {
12665                    item.label.clone()
12666                };
12667                let len = text.len();
12668                Some(language::CodeLabel {
12669                    text,
12670                    runs: Vec::new(),
12671                    filter_range: 0..len,
12672                })
12673            })),
12674            ..FakeLspAdapter::default()
12675        },
12676    );
12677    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12678    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12679    let worktree_id = workspace
12680        .update(cx, |workspace, _window, cx| {
12681            workspace.project().update(cx, |project, cx| {
12682                project.worktrees(cx).next().unwrap().read(cx).id()
12683            })
12684        })
12685        .unwrap();
12686    let _buffer = project
12687        .update(cx, |project, cx| {
12688            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12689        })
12690        .await
12691        .unwrap();
12692    let editor = workspace
12693        .update(cx, |workspace, window, cx| {
12694            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12695        })
12696        .unwrap()
12697        .await
12698        .unwrap()
12699        .downcast::<Editor>()
12700        .unwrap();
12701    let fake_server = fake_servers.next().await.unwrap();
12702
12703    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12704    let multiline_label_2 = "a\nb\nc\n";
12705    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12706    let multiline_description = "d\ne\nf\n";
12707    let multiline_detail_2 = "g\nh\ni\n";
12708
12709    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12710        move |params, _| async move {
12711            Ok(Some(lsp::CompletionResponse::Array(vec![
12712                lsp::CompletionItem {
12713                    label: multiline_label.to_string(),
12714                    text_edit: gen_text_edit(&params, "new_text_1"),
12715                    ..lsp::CompletionItem::default()
12716                },
12717                lsp::CompletionItem {
12718                    label: "single line label 1".to_string(),
12719                    detail: Some(multiline_detail.to_string()),
12720                    text_edit: gen_text_edit(&params, "new_text_2"),
12721                    ..lsp::CompletionItem::default()
12722                },
12723                lsp::CompletionItem {
12724                    label: "single line label 2".to_string(),
12725                    label_details: Some(lsp::CompletionItemLabelDetails {
12726                        description: Some(multiline_description.to_string()),
12727                        detail: None,
12728                    }),
12729                    text_edit: gen_text_edit(&params, "new_text_2"),
12730                    ..lsp::CompletionItem::default()
12731                },
12732                lsp::CompletionItem {
12733                    label: multiline_label_2.to_string(),
12734                    detail: Some(multiline_detail_2.to_string()),
12735                    text_edit: gen_text_edit(&params, "new_text_3"),
12736                    ..lsp::CompletionItem::default()
12737                },
12738                lsp::CompletionItem {
12739                    label: "Label with many     spaces and \t but without newlines".to_string(),
12740                    detail: Some(
12741                        "Details with many     spaces and \t but without newlines".to_string(),
12742                    ),
12743                    text_edit: gen_text_edit(&params, "new_text_4"),
12744                    ..lsp::CompletionItem::default()
12745                },
12746            ])))
12747        },
12748    );
12749
12750    editor.update_in(cx, |editor, window, cx| {
12751        cx.focus_self(window);
12752        editor.move_to_end(&MoveToEnd, window, cx);
12753        editor.handle_input(".", window, cx);
12754    });
12755    cx.run_until_parked();
12756    completion_handle.next().await.unwrap();
12757
12758    editor.update(cx, |editor, _| {
12759        assert!(editor.context_menu_visible());
12760        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12761        {
12762            let completion_labels = menu
12763                .completions
12764                .borrow()
12765                .iter()
12766                .map(|c| c.label.text.clone())
12767                .collect::<Vec<_>>();
12768            assert_eq!(
12769                completion_labels,
12770                &[
12771                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12772                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12773                    "single line label 2 d e f ",
12774                    "a b c g h i ",
12775                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12776                ],
12777                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12778            );
12779
12780            for completion in menu
12781                .completions
12782                .borrow()
12783                .iter() {
12784                    assert_eq!(
12785                        completion.label.filter_range,
12786                        0..completion.label.text.len(),
12787                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12788                    );
12789                }
12790        } else {
12791            panic!("expected completion menu to be open");
12792        }
12793    });
12794}
12795
12796#[gpui::test]
12797async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12798    init_test(cx, |_| {});
12799    let mut cx = EditorLspTestContext::new_rust(
12800        lsp::ServerCapabilities {
12801            completion_provider: Some(lsp::CompletionOptions {
12802                trigger_characters: Some(vec![".".to_string()]),
12803                ..Default::default()
12804            }),
12805            ..Default::default()
12806        },
12807        cx,
12808    )
12809    .await;
12810    cx.lsp
12811        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12812            Ok(Some(lsp::CompletionResponse::Array(vec![
12813                lsp::CompletionItem {
12814                    label: "first".into(),
12815                    ..Default::default()
12816                },
12817                lsp::CompletionItem {
12818                    label: "last".into(),
12819                    ..Default::default()
12820                },
12821            ])))
12822        });
12823    cx.set_state("variableˇ");
12824    cx.simulate_keystroke(".");
12825    cx.executor().run_until_parked();
12826
12827    cx.update_editor(|editor, _, _| {
12828        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12829        {
12830            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12831        } else {
12832            panic!("expected completion menu to be open");
12833        }
12834    });
12835
12836    cx.update_editor(|editor, window, cx| {
12837        editor.move_page_down(&MovePageDown::default(), window, cx);
12838        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12839        {
12840            assert!(
12841                menu.selected_item == 1,
12842                "expected PageDown to select the last item from the context menu"
12843            );
12844        } else {
12845            panic!("expected completion menu to stay open after PageDown");
12846        }
12847    });
12848
12849    cx.update_editor(|editor, window, cx| {
12850        editor.move_page_up(&MovePageUp::default(), window, cx);
12851        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12852        {
12853            assert!(
12854                menu.selected_item == 0,
12855                "expected PageUp to select the first item from the context menu"
12856            );
12857        } else {
12858            panic!("expected completion menu to stay open after PageUp");
12859        }
12860    });
12861}
12862
12863#[gpui::test]
12864async fn test_as_is_completions(cx: &mut TestAppContext) {
12865    init_test(cx, |_| {});
12866    let mut cx = EditorLspTestContext::new_rust(
12867        lsp::ServerCapabilities {
12868            completion_provider: Some(lsp::CompletionOptions {
12869                ..Default::default()
12870            }),
12871            ..Default::default()
12872        },
12873        cx,
12874    )
12875    .await;
12876    cx.lsp
12877        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12878            Ok(Some(lsp::CompletionResponse::Array(vec![
12879                lsp::CompletionItem {
12880                    label: "unsafe".into(),
12881                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12882                        range: lsp::Range {
12883                            start: lsp::Position {
12884                                line: 1,
12885                                character: 2,
12886                            },
12887                            end: lsp::Position {
12888                                line: 1,
12889                                character: 3,
12890                            },
12891                        },
12892                        new_text: "unsafe".to_string(),
12893                    })),
12894                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12895                    ..Default::default()
12896                },
12897            ])))
12898        });
12899    cx.set_state("fn a() {}\n");
12900    cx.executor().run_until_parked();
12901    cx.update_editor(|editor, window, cx| {
12902        editor.show_completions(
12903            &ShowCompletions {
12904                trigger: Some("\n".into()),
12905            },
12906            window,
12907            cx,
12908        );
12909    });
12910    cx.executor().run_until_parked();
12911
12912    cx.update_editor(|editor, window, cx| {
12913        editor.confirm_completion(&Default::default(), window, cx)
12914    });
12915    cx.executor().run_until_parked();
12916    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12917}
12918
12919#[gpui::test]
12920async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12921    init_test(cx, |_| {});
12922
12923    let mut cx = EditorLspTestContext::new_rust(
12924        lsp::ServerCapabilities {
12925            completion_provider: Some(lsp::CompletionOptions {
12926                trigger_characters: Some(vec![".".to_string()]),
12927                resolve_provider: Some(true),
12928                ..Default::default()
12929            }),
12930            ..Default::default()
12931        },
12932        cx,
12933    )
12934    .await;
12935
12936    cx.set_state("fn main() { let a = 2ˇ; }");
12937    cx.simulate_keystroke(".");
12938    let completion_item = lsp::CompletionItem {
12939        label: "Some".into(),
12940        kind: Some(lsp::CompletionItemKind::SNIPPET),
12941        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12942        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12943            kind: lsp::MarkupKind::Markdown,
12944            value: "```rust\nSome(2)\n```".to_string(),
12945        })),
12946        deprecated: Some(false),
12947        sort_text: Some("Some".to_string()),
12948        filter_text: Some("Some".to_string()),
12949        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12950        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12951            range: lsp::Range {
12952                start: lsp::Position {
12953                    line: 0,
12954                    character: 22,
12955                },
12956                end: lsp::Position {
12957                    line: 0,
12958                    character: 22,
12959                },
12960            },
12961            new_text: "Some(2)".to_string(),
12962        })),
12963        additional_text_edits: Some(vec![lsp::TextEdit {
12964            range: lsp::Range {
12965                start: lsp::Position {
12966                    line: 0,
12967                    character: 20,
12968                },
12969                end: lsp::Position {
12970                    line: 0,
12971                    character: 22,
12972                },
12973            },
12974            new_text: "".to_string(),
12975        }]),
12976        ..Default::default()
12977    };
12978
12979    let closure_completion_item = completion_item.clone();
12980    let counter = Arc::new(AtomicUsize::new(0));
12981    let counter_clone = counter.clone();
12982    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12983        let task_completion_item = closure_completion_item.clone();
12984        counter_clone.fetch_add(1, atomic::Ordering::Release);
12985        async move {
12986            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12987                is_incomplete: true,
12988                item_defaults: None,
12989                items: vec![task_completion_item],
12990            })))
12991        }
12992    });
12993
12994    cx.condition(|editor, _| editor.context_menu_visible())
12995        .await;
12996    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12997    assert!(request.next().await.is_some());
12998    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12999
13000    cx.simulate_keystrokes("S o m");
13001    cx.condition(|editor, _| editor.context_menu_visible())
13002        .await;
13003    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13004    assert!(request.next().await.is_some());
13005    assert!(request.next().await.is_some());
13006    assert!(request.next().await.is_some());
13007    request.close();
13008    assert!(request.next().await.is_none());
13009    assert_eq!(
13010        counter.load(atomic::Ordering::Acquire),
13011        4,
13012        "With the completions menu open, only one LSP request should happen per input"
13013    );
13014}
13015
13016#[gpui::test]
13017async fn test_toggle_comment(cx: &mut TestAppContext) {
13018    init_test(cx, |_| {});
13019    let mut cx = EditorTestContext::new(cx).await;
13020    let language = Arc::new(Language::new(
13021        LanguageConfig {
13022            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13023            ..Default::default()
13024        },
13025        Some(tree_sitter_rust::LANGUAGE.into()),
13026    ));
13027    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13028
13029    // If multiple selections intersect a line, the line is only toggled once.
13030    cx.set_state(indoc! {"
13031        fn a() {
13032            «//b();
13033            ˇ»// «c();
13034            //ˇ»  d();
13035        }
13036    "});
13037
13038    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13039
13040    cx.assert_editor_state(indoc! {"
13041        fn a() {
13042            «b();
13043            c();
13044            ˇ» d();
13045        }
13046    "});
13047
13048    // The comment prefix is inserted at the same column for every line in a
13049    // selection.
13050    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13051
13052    cx.assert_editor_state(indoc! {"
13053        fn a() {
13054            // «b();
13055            // c();
13056            ˇ»//  d();
13057        }
13058    "});
13059
13060    // If a selection ends at the beginning of a line, that line is not toggled.
13061    cx.set_selections_state(indoc! {"
13062        fn a() {
13063            // b();
13064            «// c();
13065        ˇ»    //  d();
13066        }
13067    "});
13068
13069    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13070
13071    cx.assert_editor_state(indoc! {"
13072        fn a() {
13073            // b();
13074            «c();
13075        ˇ»    //  d();
13076        }
13077    "});
13078
13079    // If a selection span a single line and is empty, the line is toggled.
13080    cx.set_state(indoc! {"
13081        fn a() {
13082            a();
13083            b();
13084        ˇ
13085        }
13086    "});
13087
13088    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13089
13090    cx.assert_editor_state(indoc! {"
13091        fn a() {
13092            a();
13093            b();
13094        //•ˇ
13095        }
13096    "});
13097
13098    // If a selection span multiple lines, empty lines are not toggled.
13099    cx.set_state(indoc! {"
13100        fn a() {
13101            «a();
13102
13103            c();ˇ»
13104        }
13105    "});
13106
13107    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13108
13109    cx.assert_editor_state(indoc! {"
13110        fn a() {
13111            // «a();
13112
13113            // c();ˇ»
13114        }
13115    "});
13116
13117    // If a selection includes multiple comment prefixes, all lines are uncommented.
13118    cx.set_state(indoc! {"
13119        fn a() {
13120            «// a();
13121            /// b();
13122            //! c();ˇ»
13123        }
13124    "});
13125
13126    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13127
13128    cx.assert_editor_state(indoc! {"
13129        fn a() {
13130            «a();
13131            b();
13132            c();ˇ»
13133        }
13134    "});
13135}
13136
13137#[gpui::test]
13138async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13139    init_test(cx, |_| {});
13140    let mut cx = EditorTestContext::new(cx).await;
13141    let language = Arc::new(Language::new(
13142        LanguageConfig {
13143            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13144            ..Default::default()
13145        },
13146        Some(tree_sitter_rust::LANGUAGE.into()),
13147    ));
13148    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13149
13150    let toggle_comments = &ToggleComments {
13151        advance_downwards: false,
13152        ignore_indent: true,
13153    };
13154
13155    // If multiple selections intersect a line, the line is only toggled once.
13156    cx.set_state(indoc! {"
13157        fn a() {
13158        //    «b();
13159        //    c();
13160        //    ˇ» d();
13161        }
13162    "});
13163
13164    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13165
13166    cx.assert_editor_state(indoc! {"
13167        fn a() {
13168            «b();
13169            c();
13170            ˇ» d();
13171        }
13172    "});
13173
13174    // The comment prefix is inserted at the beginning of each line
13175    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13176
13177    cx.assert_editor_state(indoc! {"
13178        fn a() {
13179        //    «b();
13180        //    c();
13181        //    ˇ» d();
13182        }
13183    "});
13184
13185    // If a selection ends at the beginning of a line, that line is not toggled.
13186    cx.set_selections_state(indoc! {"
13187        fn a() {
13188        //    b();
13189        //    «c();
13190        ˇ»//     d();
13191        }
13192    "});
13193
13194    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13195
13196    cx.assert_editor_state(indoc! {"
13197        fn a() {
13198        //    b();
13199            «c();
13200        ˇ»//     d();
13201        }
13202    "});
13203
13204    // If a selection span a single line and is empty, the line is toggled.
13205    cx.set_state(indoc! {"
13206        fn a() {
13207            a();
13208            b();
13209        ˇ
13210        }
13211    "});
13212
13213    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13214
13215    cx.assert_editor_state(indoc! {"
13216        fn a() {
13217            a();
13218            b();
13219        //ˇ
13220        }
13221    "});
13222
13223    // If a selection span multiple lines, empty lines are not toggled.
13224    cx.set_state(indoc! {"
13225        fn a() {
13226            «a();
13227
13228            c();ˇ»
13229        }
13230    "});
13231
13232    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13233
13234    cx.assert_editor_state(indoc! {"
13235        fn a() {
13236        //    «a();
13237
13238        //    c();ˇ»
13239        }
13240    "});
13241
13242    // If a selection includes multiple comment prefixes, all lines are uncommented.
13243    cx.set_state(indoc! {"
13244        fn a() {
13245        //    «a();
13246        ///    b();
13247        //!    c();ˇ»
13248        }
13249    "});
13250
13251    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13252
13253    cx.assert_editor_state(indoc! {"
13254        fn a() {
13255            «a();
13256            b();
13257            c();ˇ»
13258        }
13259    "});
13260}
13261
13262#[gpui::test]
13263async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13264    init_test(cx, |_| {});
13265
13266    let language = Arc::new(Language::new(
13267        LanguageConfig {
13268            line_comments: vec!["// ".into()],
13269            ..Default::default()
13270        },
13271        Some(tree_sitter_rust::LANGUAGE.into()),
13272    ));
13273
13274    let mut cx = EditorTestContext::new(cx).await;
13275
13276    cx.language_registry().add(language.clone());
13277    cx.update_buffer(|buffer, cx| {
13278        buffer.set_language(Some(language), cx);
13279    });
13280
13281    let toggle_comments = &ToggleComments {
13282        advance_downwards: true,
13283        ignore_indent: false,
13284    };
13285
13286    // Single cursor on one line -> advance
13287    // Cursor moves horizontally 3 characters as well on non-blank line
13288    cx.set_state(indoc!(
13289        "fn a() {
13290             ˇdog();
13291             cat();
13292        }"
13293    ));
13294    cx.update_editor(|editor, window, cx| {
13295        editor.toggle_comments(toggle_comments, window, cx);
13296    });
13297    cx.assert_editor_state(indoc!(
13298        "fn a() {
13299             // dog();
13300             catˇ();
13301        }"
13302    ));
13303
13304    // Single selection on one line -> don't advance
13305    cx.set_state(indoc!(
13306        "fn a() {
13307             «dog()ˇ»;
13308             cat();
13309        }"
13310    ));
13311    cx.update_editor(|editor, window, cx| {
13312        editor.toggle_comments(toggle_comments, window, cx);
13313    });
13314    cx.assert_editor_state(indoc!(
13315        "fn a() {
13316             // «dog()ˇ»;
13317             cat();
13318        }"
13319    ));
13320
13321    // Multiple cursors on one line -> advance
13322    cx.set_state(indoc!(
13323        "fn a() {
13324             ˇdˇog();
13325             cat();
13326        }"
13327    ));
13328    cx.update_editor(|editor, window, cx| {
13329        editor.toggle_comments(toggle_comments, window, cx);
13330    });
13331    cx.assert_editor_state(indoc!(
13332        "fn a() {
13333             // dog();
13334             catˇ(ˇ);
13335        }"
13336    ));
13337
13338    // Multiple cursors on one line, with selection -> don't advance
13339    cx.set_state(indoc!(
13340        "fn a() {
13341             ˇdˇog«()ˇ»;
13342             cat();
13343        }"
13344    ));
13345    cx.update_editor(|editor, window, cx| {
13346        editor.toggle_comments(toggle_comments, window, cx);
13347    });
13348    cx.assert_editor_state(indoc!(
13349        "fn a() {
13350             // ˇdˇog«()ˇ»;
13351             cat();
13352        }"
13353    ));
13354
13355    // Single cursor on one line -> advance
13356    // Cursor moves to column 0 on blank line
13357    cx.set_state(indoc!(
13358        "fn a() {
13359             ˇdog();
13360
13361             cat();
13362        }"
13363    ));
13364    cx.update_editor(|editor, window, cx| {
13365        editor.toggle_comments(toggle_comments, window, cx);
13366    });
13367    cx.assert_editor_state(indoc!(
13368        "fn a() {
13369             // dog();
13370        ˇ
13371             cat();
13372        }"
13373    ));
13374
13375    // Single cursor on one line -> advance
13376    // Cursor starts and ends at column 0
13377    cx.set_state(indoc!(
13378        "fn a() {
13379         ˇ    dog();
13380             cat();
13381        }"
13382    ));
13383    cx.update_editor(|editor, window, cx| {
13384        editor.toggle_comments(toggle_comments, window, cx);
13385    });
13386    cx.assert_editor_state(indoc!(
13387        "fn a() {
13388             // dog();
13389         ˇ    cat();
13390        }"
13391    ));
13392}
13393
13394#[gpui::test]
13395async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13396    init_test(cx, |_| {});
13397
13398    let mut cx = EditorTestContext::new(cx).await;
13399
13400    let html_language = Arc::new(
13401        Language::new(
13402            LanguageConfig {
13403                name: "HTML".into(),
13404                block_comment: Some(("<!-- ".into(), " -->".into())),
13405                ..Default::default()
13406            },
13407            Some(tree_sitter_html::LANGUAGE.into()),
13408        )
13409        .with_injection_query(
13410            r#"
13411            (script_element
13412                (raw_text) @injection.content
13413                (#set! injection.language "javascript"))
13414            "#,
13415        )
13416        .unwrap(),
13417    );
13418
13419    let javascript_language = Arc::new(Language::new(
13420        LanguageConfig {
13421            name: "JavaScript".into(),
13422            line_comments: vec!["// ".into()],
13423            ..Default::default()
13424        },
13425        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13426    ));
13427
13428    cx.language_registry().add(html_language.clone());
13429    cx.language_registry().add(javascript_language.clone());
13430    cx.update_buffer(|buffer, cx| {
13431        buffer.set_language(Some(html_language), cx);
13432    });
13433
13434    // Toggle comments for empty selections
13435    cx.set_state(
13436        &r#"
13437            <p>A</p>ˇ
13438            <p>B</p>ˇ
13439            <p>C</p>ˇ
13440        "#
13441        .unindent(),
13442    );
13443    cx.update_editor(|editor, window, cx| {
13444        editor.toggle_comments(&ToggleComments::default(), window, cx)
13445    });
13446    cx.assert_editor_state(
13447        &r#"
13448            <!-- <p>A</p>ˇ -->
13449            <!-- <p>B</p>ˇ -->
13450            <!-- <p>C</p>ˇ -->
13451        "#
13452        .unindent(),
13453    );
13454    cx.update_editor(|editor, window, cx| {
13455        editor.toggle_comments(&ToggleComments::default(), window, cx)
13456    });
13457    cx.assert_editor_state(
13458        &r#"
13459            <p>A</p>ˇ
13460            <p>B</p>ˇ
13461            <p>C</p>ˇ
13462        "#
13463        .unindent(),
13464    );
13465
13466    // Toggle comments for mixture of empty and non-empty selections, where
13467    // multiple selections occupy a given line.
13468    cx.set_state(
13469        &r#"
13470            <p>A«</p>
13471            <p>ˇ»B</p>ˇ
13472            <p>C«</p>
13473            <p>ˇ»D</p>ˇ
13474        "#
13475        .unindent(),
13476    );
13477
13478    cx.update_editor(|editor, window, cx| {
13479        editor.toggle_comments(&ToggleComments::default(), window, cx)
13480    });
13481    cx.assert_editor_state(
13482        &r#"
13483            <!-- <p>A«</p>
13484            <p>ˇ»B</p>ˇ -->
13485            <!-- <p>C«</p>
13486            <p>ˇ»D</p>ˇ -->
13487        "#
13488        .unindent(),
13489    );
13490    cx.update_editor(|editor, window, cx| {
13491        editor.toggle_comments(&ToggleComments::default(), window, cx)
13492    });
13493    cx.assert_editor_state(
13494        &r#"
13495            <p>A«</p>
13496            <p>ˇ»B</p>ˇ
13497            <p>C«</p>
13498            <p>ˇ»D</p>ˇ
13499        "#
13500        .unindent(),
13501    );
13502
13503    // Toggle comments when different languages are active for different
13504    // selections.
13505    cx.set_state(
13506        &r#"
13507            ˇ<script>
13508                ˇvar x = new Y();
13509            ˇ</script>
13510        "#
13511        .unindent(),
13512    );
13513    cx.executor().run_until_parked();
13514    cx.update_editor(|editor, window, cx| {
13515        editor.toggle_comments(&ToggleComments::default(), window, cx)
13516    });
13517    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13518    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13519    cx.assert_editor_state(
13520        &r#"
13521            <!-- ˇ<script> -->
13522                // ˇvar x = new Y();
13523            <!-- ˇ</script> -->
13524        "#
13525        .unindent(),
13526    );
13527}
13528
13529#[gpui::test]
13530fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13531    init_test(cx, |_| {});
13532
13533    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13534    let multibuffer = cx.new(|cx| {
13535        let mut multibuffer = MultiBuffer::new(ReadWrite);
13536        multibuffer.push_excerpts(
13537            buffer.clone(),
13538            [
13539                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13540                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13541            ],
13542            cx,
13543        );
13544        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13545        multibuffer
13546    });
13547
13548    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13549    editor.update_in(cx, |editor, window, cx| {
13550        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13551        editor.change_selections(None, window, cx, |s| {
13552            s.select_ranges([
13553                Point::new(0, 0)..Point::new(0, 0),
13554                Point::new(1, 0)..Point::new(1, 0),
13555            ])
13556        });
13557
13558        editor.handle_input("X", window, cx);
13559        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13560        assert_eq!(
13561            editor.selections.ranges(cx),
13562            [
13563                Point::new(0, 1)..Point::new(0, 1),
13564                Point::new(1, 1)..Point::new(1, 1),
13565            ]
13566        );
13567
13568        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13569        editor.change_selections(None, window, cx, |s| {
13570            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13571        });
13572        editor.backspace(&Default::default(), window, cx);
13573        assert_eq!(editor.text(cx), "Xa\nbbb");
13574        assert_eq!(
13575            editor.selections.ranges(cx),
13576            [Point::new(1, 0)..Point::new(1, 0)]
13577        );
13578
13579        editor.change_selections(None, window, cx, |s| {
13580            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13581        });
13582        editor.backspace(&Default::default(), window, cx);
13583        assert_eq!(editor.text(cx), "X\nbb");
13584        assert_eq!(
13585            editor.selections.ranges(cx),
13586            [Point::new(0, 1)..Point::new(0, 1)]
13587        );
13588    });
13589}
13590
13591#[gpui::test]
13592fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13593    init_test(cx, |_| {});
13594
13595    let markers = vec![('[', ']').into(), ('(', ')').into()];
13596    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13597        indoc! {"
13598            [aaaa
13599            (bbbb]
13600            cccc)",
13601        },
13602        markers.clone(),
13603    );
13604    let excerpt_ranges = markers.into_iter().map(|marker| {
13605        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13606        ExcerptRange::new(context.clone())
13607    });
13608    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13609    let multibuffer = cx.new(|cx| {
13610        let mut multibuffer = MultiBuffer::new(ReadWrite);
13611        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13612        multibuffer
13613    });
13614
13615    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13616    editor.update_in(cx, |editor, window, cx| {
13617        let (expected_text, selection_ranges) = marked_text_ranges(
13618            indoc! {"
13619                aaaa
13620                bˇbbb
13621                bˇbbˇb
13622                cccc"
13623            },
13624            true,
13625        );
13626        assert_eq!(editor.text(cx), expected_text);
13627        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
13628
13629        editor.handle_input("X", window, cx);
13630
13631        let (expected_text, expected_selections) = marked_text_ranges(
13632            indoc! {"
13633                aaaa
13634                bXˇbbXb
13635                bXˇbbXˇb
13636                cccc"
13637            },
13638            false,
13639        );
13640        assert_eq!(editor.text(cx), expected_text);
13641        assert_eq!(editor.selections.ranges(cx), expected_selections);
13642
13643        editor.newline(&Newline, window, cx);
13644        let (expected_text, expected_selections) = marked_text_ranges(
13645            indoc! {"
13646                aaaa
13647                bX
13648                ˇbbX
13649                b
13650                bX
13651                ˇbbX
13652                ˇb
13653                cccc"
13654            },
13655            false,
13656        );
13657        assert_eq!(editor.text(cx), expected_text);
13658        assert_eq!(editor.selections.ranges(cx), expected_selections);
13659    });
13660}
13661
13662#[gpui::test]
13663fn test_refresh_selections(cx: &mut TestAppContext) {
13664    init_test(cx, |_| {});
13665
13666    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13667    let mut excerpt1_id = None;
13668    let multibuffer = cx.new(|cx| {
13669        let mut multibuffer = MultiBuffer::new(ReadWrite);
13670        excerpt1_id = multibuffer
13671            .push_excerpts(
13672                buffer.clone(),
13673                [
13674                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13675                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13676                ],
13677                cx,
13678            )
13679            .into_iter()
13680            .next();
13681        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13682        multibuffer
13683    });
13684
13685    let editor = cx.add_window(|window, cx| {
13686        let mut editor = build_editor(multibuffer.clone(), window, cx);
13687        let snapshot = editor.snapshot(window, cx);
13688        editor.change_selections(None, window, cx, |s| {
13689            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13690        });
13691        editor.begin_selection(
13692            Point::new(2, 1).to_display_point(&snapshot),
13693            true,
13694            1,
13695            window,
13696            cx,
13697        );
13698        assert_eq!(
13699            editor.selections.ranges(cx),
13700            [
13701                Point::new(1, 3)..Point::new(1, 3),
13702                Point::new(2, 1)..Point::new(2, 1),
13703            ]
13704        );
13705        editor
13706    });
13707
13708    // Refreshing selections is a no-op when excerpts haven't changed.
13709    _ = editor.update(cx, |editor, window, cx| {
13710        editor.change_selections(None, window, cx, |s| s.refresh());
13711        assert_eq!(
13712            editor.selections.ranges(cx),
13713            [
13714                Point::new(1, 3)..Point::new(1, 3),
13715                Point::new(2, 1)..Point::new(2, 1),
13716            ]
13717        );
13718    });
13719
13720    multibuffer.update(cx, |multibuffer, cx| {
13721        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13722    });
13723    _ = editor.update(cx, |editor, window, cx| {
13724        // Removing an excerpt causes the first selection to become degenerate.
13725        assert_eq!(
13726            editor.selections.ranges(cx),
13727            [
13728                Point::new(0, 0)..Point::new(0, 0),
13729                Point::new(0, 1)..Point::new(0, 1)
13730            ]
13731        );
13732
13733        // Refreshing selections will relocate the first selection to the original buffer
13734        // location.
13735        editor.change_selections(None, window, cx, |s| s.refresh());
13736        assert_eq!(
13737            editor.selections.ranges(cx),
13738            [
13739                Point::new(0, 1)..Point::new(0, 1),
13740                Point::new(0, 3)..Point::new(0, 3)
13741            ]
13742        );
13743        assert!(editor.selections.pending_anchor().is_some());
13744    });
13745}
13746
13747#[gpui::test]
13748fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13749    init_test(cx, |_| {});
13750
13751    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13752    let mut excerpt1_id = None;
13753    let multibuffer = cx.new(|cx| {
13754        let mut multibuffer = MultiBuffer::new(ReadWrite);
13755        excerpt1_id = multibuffer
13756            .push_excerpts(
13757                buffer.clone(),
13758                [
13759                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13760                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13761                ],
13762                cx,
13763            )
13764            .into_iter()
13765            .next();
13766        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13767        multibuffer
13768    });
13769
13770    let editor = cx.add_window(|window, cx| {
13771        let mut editor = build_editor(multibuffer.clone(), window, cx);
13772        let snapshot = editor.snapshot(window, cx);
13773        editor.begin_selection(
13774            Point::new(1, 3).to_display_point(&snapshot),
13775            false,
13776            1,
13777            window,
13778            cx,
13779        );
13780        assert_eq!(
13781            editor.selections.ranges(cx),
13782            [Point::new(1, 3)..Point::new(1, 3)]
13783        );
13784        editor
13785    });
13786
13787    multibuffer.update(cx, |multibuffer, cx| {
13788        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13789    });
13790    _ = editor.update(cx, |editor, window, cx| {
13791        assert_eq!(
13792            editor.selections.ranges(cx),
13793            [Point::new(0, 0)..Point::new(0, 0)]
13794        );
13795
13796        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13797        editor.change_selections(None, window, cx, |s| s.refresh());
13798        assert_eq!(
13799            editor.selections.ranges(cx),
13800            [Point::new(0, 3)..Point::new(0, 3)]
13801        );
13802        assert!(editor.selections.pending_anchor().is_some());
13803    });
13804}
13805
13806#[gpui::test]
13807async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13808    init_test(cx, |_| {});
13809
13810    let language = Arc::new(
13811        Language::new(
13812            LanguageConfig {
13813                brackets: BracketPairConfig {
13814                    pairs: vec![
13815                        BracketPair {
13816                            start: "{".to_string(),
13817                            end: "}".to_string(),
13818                            close: true,
13819                            surround: true,
13820                            newline: true,
13821                        },
13822                        BracketPair {
13823                            start: "/* ".to_string(),
13824                            end: " */".to_string(),
13825                            close: true,
13826                            surround: true,
13827                            newline: true,
13828                        },
13829                    ],
13830                    ..Default::default()
13831                },
13832                ..Default::default()
13833            },
13834            Some(tree_sitter_rust::LANGUAGE.into()),
13835        )
13836        .with_indents_query("")
13837        .unwrap(),
13838    );
13839
13840    let text = concat!(
13841        "{   }\n",     //
13842        "  x\n",       //
13843        "  /*   */\n", //
13844        "x\n",         //
13845        "{{} }\n",     //
13846    );
13847
13848    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13849    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13850    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13851    editor
13852        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13853        .await;
13854
13855    editor.update_in(cx, |editor, window, cx| {
13856        editor.change_selections(None, window, cx, |s| {
13857            s.select_display_ranges([
13858                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13859                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13860                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13861            ])
13862        });
13863        editor.newline(&Newline, window, cx);
13864
13865        assert_eq!(
13866            editor.buffer().read(cx).read(cx).text(),
13867            concat!(
13868                "{ \n",    // Suppress rustfmt
13869                "\n",      //
13870                "}\n",     //
13871                "  x\n",   //
13872                "  /* \n", //
13873                "  \n",    //
13874                "  */\n",  //
13875                "x\n",     //
13876                "{{} \n",  //
13877                "}\n",     //
13878            )
13879        );
13880    });
13881}
13882
13883#[gpui::test]
13884fn test_highlighted_ranges(cx: &mut TestAppContext) {
13885    init_test(cx, |_| {});
13886
13887    let editor = cx.add_window(|window, cx| {
13888        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13889        build_editor(buffer.clone(), window, cx)
13890    });
13891
13892    _ = editor.update(cx, |editor, window, cx| {
13893        struct Type1;
13894        struct Type2;
13895
13896        let buffer = editor.buffer.read(cx).snapshot(cx);
13897
13898        let anchor_range =
13899            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13900
13901        editor.highlight_background::<Type1>(
13902            &[
13903                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13904                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13905                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13906                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13907            ],
13908            |_| Hsla::red(),
13909            cx,
13910        );
13911        editor.highlight_background::<Type2>(
13912            &[
13913                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13914                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13915                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13916                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13917            ],
13918            |_| Hsla::green(),
13919            cx,
13920        );
13921
13922        let snapshot = editor.snapshot(window, cx);
13923        let mut highlighted_ranges = editor.background_highlights_in_range(
13924            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13925            &snapshot,
13926            cx.theme(),
13927        );
13928        // Enforce a consistent ordering based on color without relying on the ordering of the
13929        // highlight's `TypeId` which is non-executor.
13930        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13931        assert_eq!(
13932            highlighted_ranges,
13933            &[
13934                (
13935                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13936                    Hsla::red(),
13937                ),
13938                (
13939                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13940                    Hsla::red(),
13941                ),
13942                (
13943                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13944                    Hsla::green(),
13945                ),
13946                (
13947                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13948                    Hsla::green(),
13949                ),
13950            ]
13951        );
13952        assert_eq!(
13953            editor.background_highlights_in_range(
13954                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13955                &snapshot,
13956                cx.theme(),
13957            ),
13958            &[(
13959                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13960                Hsla::red(),
13961            )]
13962        );
13963    });
13964}
13965
13966#[gpui::test]
13967async fn test_following(cx: &mut TestAppContext) {
13968    init_test(cx, |_| {});
13969
13970    let fs = FakeFs::new(cx.executor());
13971    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13972
13973    let buffer = project.update(cx, |project, cx| {
13974        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13975        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13976    });
13977    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13978    let follower = cx.update(|cx| {
13979        cx.open_window(
13980            WindowOptions {
13981                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13982                    gpui::Point::new(px(0.), px(0.)),
13983                    gpui::Point::new(px(10.), px(80.)),
13984                ))),
13985                ..Default::default()
13986            },
13987            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13988        )
13989        .unwrap()
13990    });
13991
13992    let is_still_following = Rc::new(RefCell::new(true));
13993    let follower_edit_event_count = Rc::new(RefCell::new(0));
13994    let pending_update = Rc::new(RefCell::new(None));
13995    let leader_entity = leader.root(cx).unwrap();
13996    let follower_entity = follower.root(cx).unwrap();
13997    _ = follower.update(cx, {
13998        let update = pending_update.clone();
13999        let is_still_following = is_still_following.clone();
14000        let follower_edit_event_count = follower_edit_event_count.clone();
14001        |_, window, cx| {
14002            cx.subscribe_in(
14003                &leader_entity,
14004                window,
14005                move |_, leader, event, window, cx| {
14006                    leader.read(cx).add_event_to_update_proto(
14007                        event,
14008                        &mut update.borrow_mut(),
14009                        window,
14010                        cx,
14011                    );
14012                },
14013            )
14014            .detach();
14015
14016            cx.subscribe_in(
14017                &follower_entity,
14018                window,
14019                move |_, _, event: &EditorEvent, _window, _cx| {
14020                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14021                        *is_still_following.borrow_mut() = false;
14022                    }
14023
14024                    if let EditorEvent::BufferEdited = event {
14025                        *follower_edit_event_count.borrow_mut() += 1;
14026                    }
14027                },
14028            )
14029            .detach();
14030        }
14031    });
14032
14033    // Update the selections only
14034    _ = leader.update(cx, |leader, window, cx| {
14035        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
14036    });
14037    follower
14038        .update(cx, |follower, window, cx| {
14039            follower.apply_update_proto(
14040                &project,
14041                pending_update.borrow_mut().take().unwrap(),
14042                window,
14043                cx,
14044            )
14045        })
14046        .unwrap()
14047        .await
14048        .unwrap();
14049    _ = follower.update(cx, |follower, _, cx| {
14050        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14051    });
14052    assert!(*is_still_following.borrow());
14053    assert_eq!(*follower_edit_event_count.borrow(), 0);
14054
14055    // Update the scroll position only
14056    _ = leader.update(cx, |leader, window, cx| {
14057        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14058    });
14059    follower
14060        .update(cx, |follower, window, cx| {
14061            follower.apply_update_proto(
14062                &project,
14063                pending_update.borrow_mut().take().unwrap(),
14064                window,
14065                cx,
14066            )
14067        })
14068        .unwrap()
14069        .await
14070        .unwrap();
14071    assert_eq!(
14072        follower
14073            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14074            .unwrap(),
14075        gpui::Point::new(1.5, 3.5)
14076    );
14077    assert!(*is_still_following.borrow());
14078    assert_eq!(*follower_edit_event_count.borrow(), 0);
14079
14080    // Update the selections and scroll position. The follower's scroll position is updated
14081    // via autoscroll, not via the leader's exact scroll position.
14082    _ = leader.update(cx, |leader, window, cx| {
14083        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
14084        leader.request_autoscroll(Autoscroll::newest(), cx);
14085        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14086    });
14087    follower
14088        .update(cx, |follower, window, cx| {
14089            follower.apply_update_proto(
14090                &project,
14091                pending_update.borrow_mut().take().unwrap(),
14092                window,
14093                cx,
14094            )
14095        })
14096        .unwrap()
14097        .await
14098        .unwrap();
14099    _ = follower.update(cx, |follower, _, cx| {
14100        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14101        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14102    });
14103    assert!(*is_still_following.borrow());
14104
14105    // Creating a pending selection that precedes another selection
14106    _ = leader.update(cx, |leader, window, cx| {
14107        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
14108        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14109    });
14110    follower
14111        .update(cx, |follower, window, cx| {
14112            follower.apply_update_proto(
14113                &project,
14114                pending_update.borrow_mut().take().unwrap(),
14115                window,
14116                cx,
14117            )
14118        })
14119        .unwrap()
14120        .await
14121        .unwrap();
14122    _ = follower.update(cx, |follower, _, cx| {
14123        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14124    });
14125    assert!(*is_still_following.borrow());
14126
14127    // Extend the pending selection so that it surrounds another selection
14128    _ = leader.update(cx, |leader, window, cx| {
14129        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14130    });
14131    follower
14132        .update(cx, |follower, window, cx| {
14133            follower.apply_update_proto(
14134                &project,
14135                pending_update.borrow_mut().take().unwrap(),
14136                window,
14137                cx,
14138            )
14139        })
14140        .unwrap()
14141        .await
14142        .unwrap();
14143    _ = follower.update(cx, |follower, _, cx| {
14144        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14145    });
14146
14147    // Scrolling locally breaks the follow
14148    _ = follower.update(cx, |follower, window, cx| {
14149        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14150        follower.set_scroll_anchor(
14151            ScrollAnchor {
14152                anchor: top_anchor,
14153                offset: gpui::Point::new(0.0, 0.5),
14154            },
14155            window,
14156            cx,
14157        );
14158    });
14159    assert!(!(*is_still_following.borrow()));
14160}
14161
14162#[gpui::test]
14163async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14164    init_test(cx, |_| {});
14165
14166    let fs = FakeFs::new(cx.executor());
14167    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14168    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14169    let pane = workspace
14170        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14171        .unwrap();
14172
14173    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14174
14175    let leader = pane.update_in(cx, |_, window, cx| {
14176        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14177        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14178    });
14179
14180    // Start following the editor when it has no excerpts.
14181    let mut state_message =
14182        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14183    let workspace_entity = workspace.root(cx).unwrap();
14184    let follower_1 = cx
14185        .update_window(*workspace.deref(), |_, window, cx| {
14186            Editor::from_state_proto(
14187                workspace_entity,
14188                ViewId {
14189                    creator: CollaboratorId::PeerId(PeerId::default()),
14190                    id: 0,
14191                },
14192                &mut state_message,
14193                window,
14194                cx,
14195            )
14196        })
14197        .unwrap()
14198        .unwrap()
14199        .await
14200        .unwrap();
14201
14202    let update_message = Rc::new(RefCell::new(None));
14203    follower_1.update_in(cx, {
14204        let update = update_message.clone();
14205        |_, window, cx| {
14206            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14207                leader.read(cx).add_event_to_update_proto(
14208                    event,
14209                    &mut update.borrow_mut(),
14210                    window,
14211                    cx,
14212                );
14213            })
14214            .detach();
14215        }
14216    });
14217
14218    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14219        (
14220            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14221            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14222        )
14223    });
14224
14225    // Insert some excerpts.
14226    leader.update(cx, |leader, cx| {
14227        leader.buffer.update(cx, |multibuffer, cx| {
14228            multibuffer.set_excerpts_for_path(
14229                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14230                buffer_1.clone(),
14231                vec![
14232                    Point::row_range(0..3),
14233                    Point::row_range(1..6),
14234                    Point::row_range(12..15),
14235                ],
14236                0,
14237                cx,
14238            );
14239            multibuffer.set_excerpts_for_path(
14240                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14241                buffer_2.clone(),
14242                vec![Point::row_range(0..6), Point::row_range(8..12)],
14243                0,
14244                cx,
14245            );
14246        });
14247    });
14248
14249    // Apply the update of adding the excerpts.
14250    follower_1
14251        .update_in(cx, |follower, window, cx| {
14252            follower.apply_update_proto(
14253                &project,
14254                update_message.borrow().clone().unwrap(),
14255                window,
14256                cx,
14257            )
14258        })
14259        .await
14260        .unwrap();
14261    assert_eq!(
14262        follower_1.update(cx, |editor, cx| editor.text(cx)),
14263        leader.update(cx, |editor, cx| editor.text(cx))
14264    );
14265    update_message.borrow_mut().take();
14266
14267    // Start following separately after it already has excerpts.
14268    let mut state_message =
14269        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14270    let workspace_entity = workspace.root(cx).unwrap();
14271    let follower_2 = cx
14272        .update_window(*workspace.deref(), |_, window, cx| {
14273            Editor::from_state_proto(
14274                workspace_entity,
14275                ViewId {
14276                    creator: CollaboratorId::PeerId(PeerId::default()),
14277                    id: 0,
14278                },
14279                &mut state_message,
14280                window,
14281                cx,
14282            )
14283        })
14284        .unwrap()
14285        .unwrap()
14286        .await
14287        .unwrap();
14288    assert_eq!(
14289        follower_2.update(cx, |editor, cx| editor.text(cx)),
14290        leader.update(cx, |editor, cx| editor.text(cx))
14291    );
14292
14293    // Remove some excerpts.
14294    leader.update(cx, |leader, cx| {
14295        leader.buffer.update(cx, |multibuffer, cx| {
14296            let excerpt_ids = multibuffer.excerpt_ids();
14297            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14298            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14299        });
14300    });
14301
14302    // Apply the update of removing the excerpts.
14303    follower_1
14304        .update_in(cx, |follower, window, cx| {
14305            follower.apply_update_proto(
14306                &project,
14307                update_message.borrow().clone().unwrap(),
14308                window,
14309                cx,
14310            )
14311        })
14312        .await
14313        .unwrap();
14314    follower_2
14315        .update_in(cx, |follower, window, cx| {
14316            follower.apply_update_proto(
14317                &project,
14318                update_message.borrow().clone().unwrap(),
14319                window,
14320                cx,
14321            )
14322        })
14323        .await
14324        .unwrap();
14325    update_message.borrow_mut().take();
14326    assert_eq!(
14327        follower_1.update(cx, |editor, cx| editor.text(cx)),
14328        leader.update(cx, |editor, cx| editor.text(cx))
14329    );
14330}
14331
14332#[gpui::test]
14333async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14334    init_test(cx, |_| {});
14335
14336    let mut cx = EditorTestContext::new(cx).await;
14337    let lsp_store =
14338        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14339
14340    cx.set_state(indoc! {"
14341        ˇfn func(abc def: i32) -> u32 {
14342        }
14343    "});
14344
14345    cx.update(|_, cx| {
14346        lsp_store.update(cx, |lsp_store, cx| {
14347            lsp_store
14348                .update_diagnostics(
14349                    LanguageServerId(0),
14350                    lsp::PublishDiagnosticsParams {
14351                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14352                        version: None,
14353                        diagnostics: vec![
14354                            lsp::Diagnostic {
14355                                range: lsp::Range::new(
14356                                    lsp::Position::new(0, 11),
14357                                    lsp::Position::new(0, 12),
14358                                ),
14359                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14360                                ..Default::default()
14361                            },
14362                            lsp::Diagnostic {
14363                                range: lsp::Range::new(
14364                                    lsp::Position::new(0, 12),
14365                                    lsp::Position::new(0, 15),
14366                                ),
14367                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14368                                ..Default::default()
14369                            },
14370                            lsp::Diagnostic {
14371                                range: lsp::Range::new(
14372                                    lsp::Position::new(0, 25),
14373                                    lsp::Position::new(0, 28),
14374                                ),
14375                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14376                                ..Default::default()
14377                            },
14378                        ],
14379                    },
14380                    None,
14381                    DiagnosticSourceKind::Pushed,
14382                    &[],
14383                    cx,
14384                )
14385                .unwrap()
14386        });
14387    });
14388
14389    executor.run_until_parked();
14390
14391    cx.update_editor(|editor, window, cx| {
14392        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14393    });
14394
14395    cx.assert_editor_state(indoc! {"
14396        fn func(abc def: i32) -> ˇu32 {
14397        }
14398    "});
14399
14400    cx.update_editor(|editor, window, cx| {
14401        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14402    });
14403
14404    cx.assert_editor_state(indoc! {"
14405        fn func(abc ˇdef: i32) -> u32 {
14406        }
14407    "});
14408
14409    cx.update_editor(|editor, window, cx| {
14410        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14411    });
14412
14413    cx.assert_editor_state(indoc! {"
14414        fn func(abcˇ def: i32) -> u32 {
14415        }
14416    "});
14417
14418    cx.update_editor(|editor, window, cx| {
14419        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14420    });
14421
14422    cx.assert_editor_state(indoc! {"
14423        fn func(abc def: i32) -> ˇu32 {
14424        }
14425    "});
14426}
14427
14428#[gpui::test]
14429async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14430    init_test(cx, |_| {});
14431
14432    let mut cx = EditorTestContext::new(cx).await;
14433
14434    let diff_base = r#"
14435        use some::mod;
14436
14437        const A: u32 = 42;
14438
14439        fn main() {
14440            println!("hello");
14441
14442            println!("world");
14443        }
14444        "#
14445    .unindent();
14446
14447    // Edits are modified, removed, modified, added
14448    cx.set_state(
14449        &r#"
14450        use some::modified;
14451
14452        ˇ
14453        fn main() {
14454            println!("hello there");
14455
14456            println!("around the");
14457            println!("world");
14458        }
14459        "#
14460        .unindent(),
14461    );
14462
14463    cx.set_head_text(&diff_base);
14464    executor.run_until_parked();
14465
14466    cx.update_editor(|editor, window, cx| {
14467        //Wrap around the bottom of the buffer
14468        for _ in 0..3 {
14469            editor.go_to_next_hunk(&GoToHunk, window, cx);
14470        }
14471    });
14472
14473    cx.assert_editor_state(
14474        &r#"
14475        ˇuse some::modified;
14476
14477
14478        fn main() {
14479            println!("hello there");
14480
14481            println!("around the");
14482            println!("world");
14483        }
14484        "#
14485        .unindent(),
14486    );
14487
14488    cx.update_editor(|editor, window, cx| {
14489        //Wrap around the top of the buffer
14490        for _ in 0..2 {
14491            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14492        }
14493    });
14494
14495    cx.assert_editor_state(
14496        &r#"
14497        use some::modified;
14498
14499
14500        fn main() {
14501        ˇ    println!("hello there");
14502
14503            println!("around the");
14504            println!("world");
14505        }
14506        "#
14507        .unindent(),
14508    );
14509
14510    cx.update_editor(|editor, window, cx| {
14511        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14512    });
14513
14514    cx.assert_editor_state(
14515        &r#"
14516        use some::modified;
14517
14518        ˇ
14519        fn main() {
14520            println!("hello there");
14521
14522            println!("around the");
14523            println!("world");
14524        }
14525        "#
14526        .unindent(),
14527    );
14528
14529    cx.update_editor(|editor, window, cx| {
14530        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14531    });
14532
14533    cx.assert_editor_state(
14534        &r#"
14535        ˇuse some::modified;
14536
14537
14538        fn main() {
14539            println!("hello there");
14540
14541            println!("around the");
14542            println!("world");
14543        }
14544        "#
14545        .unindent(),
14546    );
14547
14548    cx.update_editor(|editor, window, cx| {
14549        for _ in 0..2 {
14550            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14551        }
14552    });
14553
14554    cx.assert_editor_state(
14555        &r#"
14556        use some::modified;
14557
14558
14559        fn main() {
14560        ˇ    println!("hello there");
14561
14562            println!("around the");
14563            println!("world");
14564        }
14565        "#
14566        .unindent(),
14567    );
14568
14569    cx.update_editor(|editor, window, cx| {
14570        editor.fold(&Fold, window, cx);
14571    });
14572
14573    cx.update_editor(|editor, window, cx| {
14574        editor.go_to_next_hunk(&GoToHunk, window, cx);
14575    });
14576
14577    cx.assert_editor_state(
14578        &r#"
14579        ˇuse some::modified;
14580
14581
14582        fn main() {
14583            println!("hello there");
14584
14585            println!("around the");
14586            println!("world");
14587        }
14588        "#
14589        .unindent(),
14590    );
14591}
14592
14593#[test]
14594fn test_split_words() {
14595    fn split(text: &str) -> Vec<&str> {
14596        split_words(text).collect()
14597    }
14598
14599    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14600    assert_eq!(split("hello_world"), &["hello_", "world"]);
14601    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14602    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14603    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14604    assert_eq!(split("helloworld"), &["helloworld"]);
14605
14606    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14607}
14608
14609#[gpui::test]
14610async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14611    init_test(cx, |_| {});
14612
14613    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14614    let mut assert = |before, after| {
14615        let _state_context = cx.set_state(before);
14616        cx.run_until_parked();
14617        cx.update_editor(|editor, window, cx| {
14618            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14619        });
14620        cx.run_until_parked();
14621        cx.assert_editor_state(after);
14622    };
14623
14624    // Outside bracket jumps to outside of matching bracket
14625    assert("console.logˇ(var);", "console.log(var)ˇ;");
14626    assert("console.log(var)ˇ;", "console.logˇ(var);");
14627
14628    // Inside bracket jumps to inside of matching bracket
14629    assert("console.log(ˇvar);", "console.log(varˇ);");
14630    assert("console.log(varˇ);", "console.log(ˇvar);");
14631
14632    // When outside a bracket and inside, favor jumping to the inside bracket
14633    assert(
14634        "console.log('foo', [1, 2, 3]ˇ);",
14635        "console.log(ˇ'foo', [1, 2, 3]);",
14636    );
14637    assert(
14638        "console.log(ˇ'foo', [1, 2, 3]);",
14639        "console.log('foo', [1, 2, 3]ˇ);",
14640    );
14641
14642    // Bias forward if two options are equally likely
14643    assert(
14644        "let result = curried_fun()ˇ();",
14645        "let result = curried_fun()()ˇ;",
14646    );
14647
14648    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14649    assert(
14650        indoc! {"
14651            function test() {
14652                console.log('test')ˇ
14653            }"},
14654        indoc! {"
14655            function test() {
14656                console.logˇ('test')
14657            }"},
14658    );
14659}
14660
14661#[gpui::test]
14662async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14663    init_test(cx, |_| {});
14664
14665    let fs = FakeFs::new(cx.executor());
14666    fs.insert_tree(
14667        path!("/a"),
14668        json!({
14669            "main.rs": "fn main() { let a = 5; }",
14670            "other.rs": "// Test file",
14671        }),
14672    )
14673    .await;
14674    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14675
14676    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14677    language_registry.add(Arc::new(Language::new(
14678        LanguageConfig {
14679            name: "Rust".into(),
14680            matcher: LanguageMatcher {
14681                path_suffixes: vec!["rs".to_string()],
14682                ..Default::default()
14683            },
14684            brackets: BracketPairConfig {
14685                pairs: vec![BracketPair {
14686                    start: "{".to_string(),
14687                    end: "}".to_string(),
14688                    close: true,
14689                    surround: true,
14690                    newline: true,
14691                }],
14692                disabled_scopes_by_bracket_ix: Vec::new(),
14693            },
14694            ..Default::default()
14695        },
14696        Some(tree_sitter_rust::LANGUAGE.into()),
14697    )));
14698    let mut fake_servers = language_registry.register_fake_lsp(
14699        "Rust",
14700        FakeLspAdapter {
14701            capabilities: lsp::ServerCapabilities {
14702                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14703                    first_trigger_character: "{".to_string(),
14704                    more_trigger_character: None,
14705                }),
14706                ..Default::default()
14707            },
14708            ..Default::default()
14709        },
14710    );
14711
14712    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14713
14714    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14715
14716    let worktree_id = workspace
14717        .update(cx, |workspace, _, cx| {
14718            workspace.project().update(cx, |project, cx| {
14719                project.worktrees(cx).next().unwrap().read(cx).id()
14720            })
14721        })
14722        .unwrap();
14723
14724    let buffer = project
14725        .update(cx, |project, cx| {
14726            project.open_local_buffer(path!("/a/main.rs"), cx)
14727        })
14728        .await
14729        .unwrap();
14730    let editor_handle = workspace
14731        .update(cx, |workspace, window, cx| {
14732            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14733        })
14734        .unwrap()
14735        .await
14736        .unwrap()
14737        .downcast::<Editor>()
14738        .unwrap();
14739
14740    cx.executor().start_waiting();
14741    let fake_server = fake_servers.next().await.unwrap();
14742
14743    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14744        |params, _| async move {
14745            assert_eq!(
14746                params.text_document_position.text_document.uri,
14747                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14748            );
14749            assert_eq!(
14750                params.text_document_position.position,
14751                lsp::Position::new(0, 21),
14752            );
14753
14754            Ok(Some(vec![lsp::TextEdit {
14755                new_text: "]".to_string(),
14756                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14757            }]))
14758        },
14759    );
14760
14761    editor_handle.update_in(cx, |editor, window, cx| {
14762        window.focus(&editor.focus_handle(cx));
14763        editor.change_selections(None, window, cx, |s| {
14764            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14765        });
14766        editor.handle_input("{", window, cx);
14767    });
14768
14769    cx.executor().run_until_parked();
14770
14771    buffer.update(cx, |buffer, _| {
14772        assert_eq!(
14773            buffer.text(),
14774            "fn main() { let a = {5}; }",
14775            "No extra braces from on type formatting should appear in the buffer"
14776        )
14777    });
14778}
14779
14780#[gpui::test(iterations = 20, seeds(31))]
14781async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14782    init_test(cx, |_| {});
14783
14784    let mut cx = EditorLspTestContext::new_rust(
14785        lsp::ServerCapabilities {
14786            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14787                first_trigger_character: ".".to_string(),
14788                more_trigger_character: None,
14789            }),
14790            ..Default::default()
14791        },
14792        cx,
14793    )
14794    .await;
14795
14796    cx.update_buffer(|buffer, _| {
14797        // This causes autoindent to be async.
14798        buffer.set_sync_parse_timeout(Duration::ZERO)
14799    });
14800
14801    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14802    cx.simulate_keystroke("\n");
14803    cx.run_until_parked();
14804
14805    let buffer_cloned =
14806        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14807    let mut request =
14808        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14809            let buffer_cloned = buffer_cloned.clone();
14810            async move {
14811                buffer_cloned.update(&mut cx, |buffer, _| {
14812                    assert_eq!(
14813                        buffer.text(),
14814                        "fn c() {\n    d()\n        .\n}\n",
14815                        "OnTypeFormatting should triggered after autoindent applied"
14816                    )
14817                })?;
14818
14819                Ok(Some(vec![]))
14820            }
14821        });
14822
14823    cx.simulate_keystroke(".");
14824    cx.run_until_parked();
14825
14826    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14827    assert!(request.next().await.is_some());
14828    request.close();
14829    assert!(request.next().await.is_none());
14830}
14831
14832#[gpui::test]
14833async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14834    init_test(cx, |_| {});
14835
14836    let fs = FakeFs::new(cx.executor());
14837    fs.insert_tree(
14838        path!("/a"),
14839        json!({
14840            "main.rs": "fn main() { let a = 5; }",
14841            "other.rs": "// Test file",
14842        }),
14843    )
14844    .await;
14845
14846    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14847
14848    let server_restarts = Arc::new(AtomicUsize::new(0));
14849    let closure_restarts = Arc::clone(&server_restarts);
14850    let language_server_name = "test language server";
14851    let language_name: LanguageName = "Rust".into();
14852
14853    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14854    language_registry.add(Arc::new(Language::new(
14855        LanguageConfig {
14856            name: language_name.clone(),
14857            matcher: LanguageMatcher {
14858                path_suffixes: vec!["rs".to_string()],
14859                ..Default::default()
14860            },
14861            ..Default::default()
14862        },
14863        Some(tree_sitter_rust::LANGUAGE.into()),
14864    )));
14865    let mut fake_servers = language_registry.register_fake_lsp(
14866        "Rust",
14867        FakeLspAdapter {
14868            name: language_server_name,
14869            initialization_options: Some(json!({
14870                "testOptionValue": true
14871            })),
14872            initializer: Some(Box::new(move |fake_server| {
14873                let task_restarts = Arc::clone(&closure_restarts);
14874                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14875                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14876                    futures::future::ready(Ok(()))
14877                });
14878            })),
14879            ..Default::default()
14880        },
14881    );
14882
14883    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14884    let _buffer = project
14885        .update(cx, |project, cx| {
14886            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14887        })
14888        .await
14889        .unwrap();
14890    let _fake_server = fake_servers.next().await.unwrap();
14891    update_test_language_settings(cx, |language_settings| {
14892        language_settings.languages.insert(
14893            language_name.clone(),
14894            LanguageSettingsContent {
14895                tab_size: NonZeroU32::new(8),
14896                ..Default::default()
14897            },
14898        );
14899    });
14900    cx.executor().run_until_parked();
14901    assert_eq!(
14902        server_restarts.load(atomic::Ordering::Acquire),
14903        0,
14904        "Should not restart LSP server on an unrelated change"
14905    );
14906
14907    update_test_project_settings(cx, |project_settings| {
14908        project_settings.lsp.insert(
14909            "Some other server name".into(),
14910            LspSettings {
14911                binary: None,
14912                settings: None,
14913                initialization_options: Some(json!({
14914                    "some other init value": false
14915                })),
14916                enable_lsp_tasks: false,
14917            },
14918        );
14919    });
14920    cx.executor().run_until_parked();
14921    assert_eq!(
14922        server_restarts.load(atomic::Ordering::Acquire),
14923        0,
14924        "Should not restart LSP server on an unrelated LSP settings change"
14925    );
14926
14927    update_test_project_settings(cx, |project_settings| {
14928        project_settings.lsp.insert(
14929            language_server_name.into(),
14930            LspSettings {
14931                binary: None,
14932                settings: None,
14933                initialization_options: Some(json!({
14934                    "anotherInitValue": false
14935                })),
14936                enable_lsp_tasks: false,
14937            },
14938        );
14939    });
14940    cx.executor().run_until_parked();
14941    assert_eq!(
14942        server_restarts.load(atomic::Ordering::Acquire),
14943        1,
14944        "Should restart LSP server on a related LSP settings change"
14945    );
14946
14947    update_test_project_settings(cx, |project_settings| {
14948        project_settings.lsp.insert(
14949            language_server_name.into(),
14950            LspSettings {
14951                binary: None,
14952                settings: None,
14953                initialization_options: Some(json!({
14954                    "anotherInitValue": false
14955                })),
14956                enable_lsp_tasks: false,
14957            },
14958        );
14959    });
14960    cx.executor().run_until_parked();
14961    assert_eq!(
14962        server_restarts.load(atomic::Ordering::Acquire),
14963        1,
14964        "Should not restart LSP server on a related LSP settings change that is the same"
14965    );
14966
14967    update_test_project_settings(cx, |project_settings| {
14968        project_settings.lsp.insert(
14969            language_server_name.into(),
14970            LspSettings {
14971                binary: None,
14972                settings: None,
14973                initialization_options: None,
14974                enable_lsp_tasks: false,
14975            },
14976        );
14977    });
14978    cx.executor().run_until_parked();
14979    assert_eq!(
14980        server_restarts.load(atomic::Ordering::Acquire),
14981        2,
14982        "Should restart LSP server on another related LSP settings change"
14983    );
14984}
14985
14986#[gpui::test]
14987async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14988    init_test(cx, |_| {});
14989
14990    let mut cx = EditorLspTestContext::new_rust(
14991        lsp::ServerCapabilities {
14992            completion_provider: Some(lsp::CompletionOptions {
14993                trigger_characters: Some(vec![".".to_string()]),
14994                resolve_provider: Some(true),
14995                ..Default::default()
14996            }),
14997            ..Default::default()
14998        },
14999        cx,
15000    )
15001    .await;
15002
15003    cx.set_state("fn main() { let a = 2ˇ; }");
15004    cx.simulate_keystroke(".");
15005    let completion_item = lsp::CompletionItem {
15006        label: "some".into(),
15007        kind: Some(lsp::CompletionItemKind::SNIPPET),
15008        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15009        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15010            kind: lsp::MarkupKind::Markdown,
15011            value: "```rust\nSome(2)\n```".to_string(),
15012        })),
15013        deprecated: Some(false),
15014        sort_text: Some("fffffff2".to_string()),
15015        filter_text: Some("some".to_string()),
15016        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15017        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15018            range: lsp::Range {
15019                start: lsp::Position {
15020                    line: 0,
15021                    character: 22,
15022                },
15023                end: lsp::Position {
15024                    line: 0,
15025                    character: 22,
15026                },
15027            },
15028            new_text: "Some(2)".to_string(),
15029        })),
15030        additional_text_edits: Some(vec![lsp::TextEdit {
15031            range: lsp::Range {
15032                start: lsp::Position {
15033                    line: 0,
15034                    character: 20,
15035                },
15036                end: lsp::Position {
15037                    line: 0,
15038                    character: 22,
15039                },
15040            },
15041            new_text: "".to_string(),
15042        }]),
15043        ..Default::default()
15044    };
15045
15046    let closure_completion_item = completion_item.clone();
15047    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15048        let task_completion_item = closure_completion_item.clone();
15049        async move {
15050            Ok(Some(lsp::CompletionResponse::Array(vec![
15051                task_completion_item,
15052            ])))
15053        }
15054    });
15055
15056    request.next().await;
15057
15058    cx.condition(|editor, _| editor.context_menu_visible())
15059        .await;
15060    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15061        editor
15062            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15063            .unwrap()
15064    });
15065    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15066
15067    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15068        let task_completion_item = completion_item.clone();
15069        async move { Ok(task_completion_item) }
15070    })
15071    .next()
15072    .await
15073    .unwrap();
15074    apply_additional_edits.await.unwrap();
15075    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15076}
15077
15078#[gpui::test]
15079async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15080    init_test(cx, |_| {});
15081
15082    let mut cx = EditorLspTestContext::new_rust(
15083        lsp::ServerCapabilities {
15084            completion_provider: Some(lsp::CompletionOptions {
15085                trigger_characters: Some(vec![".".to_string()]),
15086                resolve_provider: Some(true),
15087                ..Default::default()
15088            }),
15089            ..Default::default()
15090        },
15091        cx,
15092    )
15093    .await;
15094
15095    cx.set_state("fn main() { let a = 2ˇ; }");
15096    cx.simulate_keystroke(".");
15097
15098    let item1 = lsp::CompletionItem {
15099        label: "method id()".to_string(),
15100        filter_text: Some("id".to_string()),
15101        detail: None,
15102        documentation: None,
15103        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15104            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15105            new_text: ".id".to_string(),
15106        })),
15107        ..lsp::CompletionItem::default()
15108    };
15109
15110    let item2 = lsp::CompletionItem {
15111        label: "other".to_string(),
15112        filter_text: Some("other".to_string()),
15113        detail: None,
15114        documentation: None,
15115        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15116            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15117            new_text: ".other".to_string(),
15118        })),
15119        ..lsp::CompletionItem::default()
15120    };
15121
15122    let item1 = item1.clone();
15123    cx.set_request_handler::<lsp::request::Completion, _, _>({
15124        let item1 = item1.clone();
15125        move |_, _, _| {
15126            let item1 = item1.clone();
15127            let item2 = item2.clone();
15128            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15129        }
15130    })
15131    .next()
15132    .await;
15133
15134    cx.condition(|editor, _| editor.context_menu_visible())
15135        .await;
15136    cx.update_editor(|editor, _, _| {
15137        let context_menu = editor.context_menu.borrow_mut();
15138        let context_menu = context_menu
15139            .as_ref()
15140            .expect("Should have the context menu deployed");
15141        match context_menu {
15142            CodeContextMenu::Completions(completions_menu) => {
15143                let completions = completions_menu.completions.borrow_mut();
15144                assert_eq!(
15145                    completions
15146                        .iter()
15147                        .map(|completion| &completion.label.text)
15148                        .collect::<Vec<_>>(),
15149                    vec!["method id()", "other"]
15150                )
15151            }
15152            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15153        }
15154    });
15155
15156    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15157        let item1 = item1.clone();
15158        move |_, item_to_resolve, _| {
15159            let item1 = item1.clone();
15160            async move {
15161                if item1 == item_to_resolve {
15162                    Ok(lsp::CompletionItem {
15163                        label: "method id()".to_string(),
15164                        filter_text: Some("id".to_string()),
15165                        detail: Some("Now resolved!".to_string()),
15166                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15167                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15168                            range: lsp::Range::new(
15169                                lsp::Position::new(0, 22),
15170                                lsp::Position::new(0, 22),
15171                            ),
15172                            new_text: ".id".to_string(),
15173                        })),
15174                        ..lsp::CompletionItem::default()
15175                    })
15176                } else {
15177                    Ok(item_to_resolve)
15178                }
15179            }
15180        }
15181    })
15182    .next()
15183    .await
15184    .unwrap();
15185    cx.run_until_parked();
15186
15187    cx.update_editor(|editor, window, cx| {
15188        editor.context_menu_next(&Default::default(), window, cx);
15189    });
15190
15191    cx.update_editor(|editor, _, _| {
15192        let context_menu = editor.context_menu.borrow_mut();
15193        let context_menu = context_menu
15194            .as_ref()
15195            .expect("Should have the context menu deployed");
15196        match context_menu {
15197            CodeContextMenu::Completions(completions_menu) => {
15198                let completions = completions_menu.completions.borrow_mut();
15199                assert_eq!(
15200                    completions
15201                        .iter()
15202                        .map(|completion| &completion.label.text)
15203                        .collect::<Vec<_>>(),
15204                    vec!["method id() Now resolved!", "other"],
15205                    "Should update first completion label, but not second as the filter text did not match."
15206                );
15207            }
15208            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15209        }
15210    });
15211}
15212
15213#[gpui::test]
15214async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15215    init_test(cx, |_| {});
15216    let mut cx = EditorLspTestContext::new_rust(
15217        lsp::ServerCapabilities {
15218            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15219            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15220            completion_provider: Some(lsp::CompletionOptions {
15221                resolve_provider: Some(true),
15222                ..Default::default()
15223            }),
15224            ..Default::default()
15225        },
15226        cx,
15227    )
15228    .await;
15229    cx.set_state(indoc! {"
15230        struct TestStruct {
15231            field: i32
15232        }
15233
15234        fn mainˇ() {
15235            let unused_var = 42;
15236            let test_struct = TestStruct { field: 42 };
15237        }
15238    "});
15239    let symbol_range = cx.lsp_range(indoc! {"
15240        struct TestStruct {
15241            field: i32
15242        }
15243
15244        «fn main»() {
15245            let unused_var = 42;
15246            let test_struct = TestStruct { field: 42 };
15247        }
15248    "});
15249    let mut hover_requests =
15250        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15251            Ok(Some(lsp::Hover {
15252                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15253                    kind: lsp::MarkupKind::Markdown,
15254                    value: "Function documentation".to_string(),
15255                }),
15256                range: Some(symbol_range),
15257            }))
15258        });
15259
15260    // Case 1: Test that code action menu hide hover popover
15261    cx.dispatch_action(Hover);
15262    hover_requests.next().await;
15263    cx.condition(|editor, _| editor.hover_state.visible()).await;
15264    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15265        move |_, _, _| async move {
15266            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15267                lsp::CodeAction {
15268                    title: "Remove unused variable".to_string(),
15269                    kind: Some(CodeActionKind::QUICKFIX),
15270                    edit: Some(lsp::WorkspaceEdit {
15271                        changes: Some(
15272                            [(
15273                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15274                                vec![lsp::TextEdit {
15275                                    range: lsp::Range::new(
15276                                        lsp::Position::new(5, 4),
15277                                        lsp::Position::new(5, 27),
15278                                    ),
15279                                    new_text: "".to_string(),
15280                                }],
15281                            )]
15282                            .into_iter()
15283                            .collect(),
15284                        ),
15285                        ..Default::default()
15286                    }),
15287                    ..Default::default()
15288                },
15289            )]))
15290        },
15291    );
15292    cx.update_editor(|editor, window, cx| {
15293        editor.toggle_code_actions(
15294            &ToggleCodeActions {
15295                deployed_from: None,
15296                quick_launch: false,
15297            },
15298            window,
15299            cx,
15300        );
15301    });
15302    code_action_requests.next().await;
15303    cx.run_until_parked();
15304    cx.condition(|editor, _| editor.context_menu_visible())
15305        .await;
15306    cx.update_editor(|editor, _, _| {
15307        assert!(
15308            !editor.hover_state.visible(),
15309            "Hover popover should be hidden when code action menu is shown"
15310        );
15311        // Hide code actions
15312        editor.context_menu.take();
15313    });
15314
15315    // Case 2: Test that code completions hide hover popover
15316    cx.dispatch_action(Hover);
15317    hover_requests.next().await;
15318    cx.condition(|editor, _| editor.hover_state.visible()).await;
15319    let counter = Arc::new(AtomicUsize::new(0));
15320    let mut completion_requests =
15321        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15322            let counter = counter.clone();
15323            async move {
15324                counter.fetch_add(1, atomic::Ordering::Release);
15325                Ok(Some(lsp::CompletionResponse::Array(vec![
15326                    lsp::CompletionItem {
15327                        label: "main".into(),
15328                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15329                        detail: Some("() -> ()".to_string()),
15330                        ..Default::default()
15331                    },
15332                    lsp::CompletionItem {
15333                        label: "TestStruct".into(),
15334                        kind: Some(lsp::CompletionItemKind::STRUCT),
15335                        detail: Some("struct TestStruct".to_string()),
15336                        ..Default::default()
15337                    },
15338                ])))
15339            }
15340        });
15341    cx.update_editor(|editor, window, cx| {
15342        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15343    });
15344    completion_requests.next().await;
15345    cx.condition(|editor, _| editor.context_menu_visible())
15346        .await;
15347    cx.update_editor(|editor, _, _| {
15348        assert!(
15349            !editor.hover_state.visible(),
15350            "Hover popover should be hidden when completion menu is shown"
15351        );
15352    });
15353}
15354
15355#[gpui::test]
15356async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15357    init_test(cx, |_| {});
15358
15359    let mut cx = EditorLspTestContext::new_rust(
15360        lsp::ServerCapabilities {
15361            completion_provider: Some(lsp::CompletionOptions {
15362                trigger_characters: Some(vec![".".to_string()]),
15363                resolve_provider: Some(true),
15364                ..Default::default()
15365            }),
15366            ..Default::default()
15367        },
15368        cx,
15369    )
15370    .await;
15371
15372    cx.set_state("fn main() { let a = 2ˇ; }");
15373    cx.simulate_keystroke(".");
15374
15375    let unresolved_item_1 = lsp::CompletionItem {
15376        label: "id".to_string(),
15377        filter_text: Some("id".to_string()),
15378        detail: None,
15379        documentation: None,
15380        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15381            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15382            new_text: ".id".to_string(),
15383        })),
15384        ..lsp::CompletionItem::default()
15385    };
15386    let resolved_item_1 = lsp::CompletionItem {
15387        additional_text_edits: Some(vec![lsp::TextEdit {
15388            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15389            new_text: "!!".to_string(),
15390        }]),
15391        ..unresolved_item_1.clone()
15392    };
15393    let unresolved_item_2 = lsp::CompletionItem {
15394        label: "other".to_string(),
15395        filter_text: Some("other".to_string()),
15396        detail: None,
15397        documentation: None,
15398        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15399            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15400            new_text: ".other".to_string(),
15401        })),
15402        ..lsp::CompletionItem::default()
15403    };
15404    let resolved_item_2 = lsp::CompletionItem {
15405        additional_text_edits: Some(vec![lsp::TextEdit {
15406            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15407            new_text: "??".to_string(),
15408        }]),
15409        ..unresolved_item_2.clone()
15410    };
15411
15412    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15413    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15414    cx.lsp
15415        .server
15416        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15417            let unresolved_item_1 = unresolved_item_1.clone();
15418            let resolved_item_1 = resolved_item_1.clone();
15419            let unresolved_item_2 = unresolved_item_2.clone();
15420            let resolved_item_2 = resolved_item_2.clone();
15421            let resolve_requests_1 = resolve_requests_1.clone();
15422            let resolve_requests_2 = resolve_requests_2.clone();
15423            move |unresolved_request, _| {
15424                let unresolved_item_1 = unresolved_item_1.clone();
15425                let resolved_item_1 = resolved_item_1.clone();
15426                let unresolved_item_2 = unresolved_item_2.clone();
15427                let resolved_item_2 = resolved_item_2.clone();
15428                let resolve_requests_1 = resolve_requests_1.clone();
15429                let resolve_requests_2 = resolve_requests_2.clone();
15430                async move {
15431                    if unresolved_request == unresolved_item_1 {
15432                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15433                        Ok(resolved_item_1.clone())
15434                    } else if unresolved_request == unresolved_item_2 {
15435                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15436                        Ok(resolved_item_2.clone())
15437                    } else {
15438                        panic!("Unexpected completion item {unresolved_request:?}")
15439                    }
15440                }
15441            }
15442        })
15443        .detach();
15444
15445    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15446        let unresolved_item_1 = unresolved_item_1.clone();
15447        let unresolved_item_2 = unresolved_item_2.clone();
15448        async move {
15449            Ok(Some(lsp::CompletionResponse::Array(vec![
15450                unresolved_item_1,
15451                unresolved_item_2,
15452            ])))
15453        }
15454    })
15455    .next()
15456    .await;
15457
15458    cx.condition(|editor, _| editor.context_menu_visible())
15459        .await;
15460    cx.update_editor(|editor, _, _| {
15461        let context_menu = editor.context_menu.borrow_mut();
15462        let context_menu = context_menu
15463            .as_ref()
15464            .expect("Should have the context menu deployed");
15465        match context_menu {
15466            CodeContextMenu::Completions(completions_menu) => {
15467                let completions = completions_menu.completions.borrow_mut();
15468                assert_eq!(
15469                    completions
15470                        .iter()
15471                        .map(|completion| &completion.label.text)
15472                        .collect::<Vec<_>>(),
15473                    vec!["id", "other"]
15474                )
15475            }
15476            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15477        }
15478    });
15479    cx.run_until_parked();
15480
15481    cx.update_editor(|editor, window, cx| {
15482        editor.context_menu_next(&ContextMenuNext, window, cx);
15483    });
15484    cx.run_until_parked();
15485    cx.update_editor(|editor, window, cx| {
15486        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15487    });
15488    cx.run_until_parked();
15489    cx.update_editor(|editor, window, cx| {
15490        editor.context_menu_next(&ContextMenuNext, window, cx);
15491    });
15492    cx.run_until_parked();
15493    cx.update_editor(|editor, window, cx| {
15494        editor
15495            .compose_completion(&ComposeCompletion::default(), window, cx)
15496            .expect("No task returned")
15497    })
15498    .await
15499    .expect("Completion failed");
15500    cx.run_until_parked();
15501
15502    cx.update_editor(|editor, _, cx| {
15503        assert_eq!(
15504            resolve_requests_1.load(atomic::Ordering::Acquire),
15505            1,
15506            "Should always resolve once despite multiple selections"
15507        );
15508        assert_eq!(
15509            resolve_requests_2.load(atomic::Ordering::Acquire),
15510            1,
15511            "Should always resolve once after multiple selections and applying the completion"
15512        );
15513        assert_eq!(
15514            editor.text(cx),
15515            "fn main() { let a = ??.other; }",
15516            "Should use resolved data when applying the completion"
15517        );
15518    });
15519}
15520
15521#[gpui::test]
15522async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15523    init_test(cx, |_| {});
15524
15525    let item_0 = lsp::CompletionItem {
15526        label: "abs".into(),
15527        insert_text: Some("abs".into()),
15528        data: Some(json!({ "very": "special"})),
15529        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15530        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15531            lsp::InsertReplaceEdit {
15532                new_text: "abs".to_string(),
15533                insert: lsp::Range::default(),
15534                replace: lsp::Range::default(),
15535            },
15536        )),
15537        ..lsp::CompletionItem::default()
15538    };
15539    let items = iter::once(item_0.clone())
15540        .chain((11..51).map(|i| lsp::CompletionItem {
15541            label: format!("item_{}", i),
15542            insert_text: Some(format!("item_{}", i)),
15543            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15544            ..lsp::CompletionItem::default()
15545        }))
15546        .collect::<Vec<_>>();
15547
15548    let default_commit_characters = vec!["?".to_string()];
15549    let default_data = json!({ "default": "data"});
15550    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15551    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15552    let default_edit_range = lsp::Range {
15553        start: lsp::Position {
15554            line: 0,
15555            character: 5,
15556        },
15557        end: lsp::Position {
15558            line: 0,
15559            character: 5,
15560        },
15561    };
15562
15563    let mut cx = EditorLspTestContext::new_rust(
15564        lsp::ServerCapabilities {
15565            completion_provider: Some(lsp::CompletionOptions {
15566                trigger_characters: Some(vec![".".to_string()]),
15567                resolve_provider: Some(true),
15568                ..Default::default()
15569            }),
15570            ..Default::default()
15571        },
15572        cx,
15573    )
15574    .await;
15575
15576    cx.set_state("fn main() { let a = 2ˇ; }");
15577    cx.simulate_keystroke(".");
15578
15579    let completion_data = default_data.clone();
15580    let completion_characters = default_commit_characters.clone();
15581    let completion_items = items.clone();
15582    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15583        let default_data = completion_data.clone();
15584        let default_commit_characters = completion_characters.clone();
15585        let items = completion_items.clone();
15586        async move {
15587            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15588                items,
15589                item_defaults: Some(lsp::CompletionListItemDefaults {
15590                    data: Some(default_data.clone()),
15591                    commit_characters: Some(default_commit_characters.clone()),
15592                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15593                        default_edit_range,
15594                    )),
15595                    insert_text_format: Some(default_insert_text_format),
15596                    insert_text_mode: Some(default_insert_text_mode),
15597                }),
15598                ..lsp::CompletionList::default()
15599            })))
15600        }
15601    })
15602    .next()
15603    .await;
15604
15605    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15606    cx.lsp
15607        .server
15608        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15609            let closure_resolved_items = resolved_items.clone();
15610            move |item_to_resolve, _| {
15611                let closure_resolved_items = closure_resolved_items.clone();
15612                async move {
15613                    closure_resolved_items.lock().push(item_to_resolve.clone());
15614                    Ok(item_to_resolve)
15615                }
15616            }
15617        })
15618        .detach();
15619
15620    cx.condition(|editor, _| editor.context_menu_visible())
15621        .await;
15622    cx.run_until_parked();
15623    cx.update_editor(|editor, _, _| {
15624        let menu = editor.context_menu.borrow_mut();
15625        match menu.as_ref().expect("should have the completions menu") {
15626            CodeContextMenu::Completions(completions_menu) => {
15627                assert_eq!(
15628                    completions_menu
15629                        .entries
15630                        .borrow()
15631                        .iter()
15632                        .map(|mat| mat.string.clone())
15633                        .collect::<Vec<String>>(),
15634                    items
15635                        .iter()
15636                        .map(|completion| completion.label.clone())
15637                        .collect::<Vec<String>>()
15638                );
15639            }
15640            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15641        }
15642    });
15643    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15644    // with 4 from the end.
15645    assert_eq!(
15646        *resolved_items.lock(),
15647        [&items[0..16], &items[items.len() - 4..items.len()]]
15648            .concat()
15649            .iter()
15650            .cloned()
15651            .map(|mut item| {
15652                if item.data.is_none() {
15653                    item.data = Some(default_data.clone());
15654                }
15655                item
15656            })
15657            .collect::<Vec<lsp::CompletionItem>>(),
15658        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15659    );
15660    resolved_items.lock().clear();
15661
15662    cx.update_editor(|editor, window, cx| {
15663        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15664    });
15665    cx.run_until_parked();
15666    // Completions that have already been resolved are skipped.
15667    assert_eq!(
15668        *resolved_items.lock(),
15669        items[items.len() - 17..items.len() - 4]
15670            .iter()
15671            .cloned()
15672            .map(|mut item| {
15673                if item.data.is_none() {
15674                    item.data = Some(default_data.clone());
15675                }
15676                item
15677            })
15678            .collect::<Vec<lsp::CompletionItem>>()
15679    );
15680    resolved_items.lock().clear();
15681}
15682
15683#[gpui::test]
15684async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15685    init_test(cx, |_| {});
15686
15687    let mut cx = EditorLspTestContext::new(
15688        Language::new(
15689            LanguageConfig {
15690                matcher: LanguageMatcher {
15691                    path_suffixes: vec!["jsx".into()],
15692                    ..Default::default()
15693                },
15694                overrides: [(
15695                    "element".into(),
15696                    LanguageConfigOverride {
15697                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15698                        ..Default::default()
15699                    },
15700                )]
15701                .into_iter()
15702                .collect(),
15703                ..Default::default()
15704            },
15705            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15706        )
15707        .with_override_query("(jsx_self_closing_element) @element")
15708        .unwrap(),
15709        lsp::ServerCapabilities {
15710            completion_provider: Some(lsp::CompletionOptions {
15711                trigger_characters: Some(vec![":".to_string()]),
15712                ..Default::default()
15713            }),
15714            ..Default::default()
15715        },
15716        cx,
15717    )
15718    .await;
15719
15720    cx.lsp
15721        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15722            Ok(Some(lsp::CompletionResponse::Array(vec![
15723                lsp::CompletionItem {
15724                    label: "bg-blue".into(),
15725                    ..Default::default()
15726                },
15727                lsp::CompletionItem {
15728                    label: "bg-red".into(),
15729                    ..Default::default()
15730                },
15731                lsp::CompletionItem {
15732                    label: "bg-yellow".into(),
15733                    ..Default::default()
15734                },
15735            ])))
15736        });
15737
15738    cx.set_state(r#"<p class="bgˇ" />"#);
15739
15740    // Trigger completion when typing a dash, because the dash is an extra
15741    // word character in the 'element' scope, which contains the cursor.
15742    cx.simulate_keystroke("-");
15743    cx.executor().run_until_parked();
15744    cx.update_editor(|editor, _, _| {
15745        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15746        {
15747            assert_eq!(
15748                completion_menu_entries(&menu),
15749                &["bg-blue", "bg-red", "bg-yellow"]
15750            );
15751        } else {
15752            panic!("expected completion menu to be open");
15753        }
15754    });
15755
15756    cx.simulate_keystroke("l");
15757    cx.executor().run_until_parked();
15758    cx.update_editor(|editor, _, _| {
15759        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15760        {
15761            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15762        } else {
15763            panic!("expected completion menu to be open");
15764        }
15765    });
15766
15767    // When filtering completions, consider the character after the '-' to
15768    // be the start of a subword.
15769    cx.set_state(r#"<p class="yelˇ" />"#);
15770    cx.simulate_keystroke("l");
15771    cx.executor().run_until_parked();
15772    cx.update_editor(|editor, _, _| {
15773        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15774        {
15775            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15776        } else {
15777            panic!("expected completion menu to be open");
15778        }
15779    });
15780}
15781
15782fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15783    let entries = menu.entries.borrow();
15784    entries.iter().map(|mat| mat.string.clone()).collect()
15785}
15786
15787#[gpui::test]
15788async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15789    init_test(cx, |settings| {
15790        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15791            FormatterList(vec![Formatter::Prettier].into()),
15792        ))
15793    });
15794
15795    let fs = FakeFs::new(cx.executor());
15796    fs.insert_file(path!("/file.ts"), Default::default()).await;
15797
15798    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15799    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15800
15801    language_registry.add(Arc::new(Language::new(
15802        LanguageConfig {
15803            name: "TypeScript".into(),
15804            matcher: LanguageMatcher {
15805                path_suffixes: vec!["ts".to_string()],
15806                ..Default::default()
15807            },
15808            ..Default::default()
15809        },
15810        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15811    )));
15812    update_test_language_settings(cx, |settings| {
15813        settings.defaults.prettier = Some(PrettierSettings {
15814            allowed: true,
15815            ..PrettierSettings::default()
15816        });
15817    });
15818
15819    let test_plugin = "test_plugin";
15820    let _ = language_registry.register_fake_lsp(
15821        "TypeScript",
15822        FakeLspAdapter {
15823            prettier_plugins: vec![test_plugin],
15824            ..Default::default()
15825        },
15826    );
15827
15828    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15829    let buffer = project
15830        .update(cx, |project, cx| {
15831            project.open_local_buffer(path!("/file.ts"), cx)
15832        })
15833        .await
15834        .unwrap();
15835
15836    let buffer_text = "one\ntwo\nthree\n";
15837    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15838    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15839    editor.update_in(cx, |editor, window, cx| {
15840        editor.set_text(buffer_text, window, cx)
15841    });
15842
15843    editor
15844        .update_in(cx, |editor, window, cx| {
15845            editor.perform_format(
15846                project.clone(),
15847                FormatTrigger::Manual,
15848                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15849                window,
15850                cx,
15851            )
15852        })
15853        .unwrap()
15854        .await;
15855    assert_eq!(
15856        editor.update(cx, |editor, cx| editor.text(cx)),
15857        buffer_text.to_string() + prettier_format_suffix,
15858        "Test prettier formatting was not applied to the original buffer text",
15859    );
15860
15861    update_test_language_settings(cx, |settings| {
15862        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15863    });
15864    let format = editor.update_in(cx, |editor, window, cx| {
15865        editor.perform_format(
15866            project.clone(),
15867            FormatTrigger::Manual,
15868            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15869            window,
15870            cx,
15871        )
15872    });
15873    format.await.unwrap();
15874    assert_eq!(
15875        editor.update(cx, |editor, cx| editor.text(cx)),
15876        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15877        "Autoformatting (via test prettier) was not applied to the original buffer text",
15878    );
15879}
15880
15881#[gpui::test]
15882async fn test_addition_reverts(cx: &mut TestAppContext) {
15883    init_test(cx, |_| {});
15884    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15885    let base_text = indoc! {r#"
15886        struct Row;
15887        struct Row1;
15888        struct Row2;
15889
15890        struct Row4;
15891        struct Row5;
15892        struct Row6;
15893
15894        struct Row8;
15895        struct Row9;
15896        struct Row10;"#};
15897
15898    // When addition hunks are not adjacent to carets, no hunk revert is performed
15899    assert_hunk_revert(
15900        indoc! {r#"struct Row;
15901                   struct Row1;
15902                   struct Row1.1;
15903                   struct Row1.2;
15904                   struct Row2;ˇ
15905
15906                   struct Row4;
15907                   struct Row5;
15908                   struct Row6;
15909
15910                   struct Row8;
15911                   ˇstruct Row9;
15912                   struct Row9.1;
15913                   struct Row9.2;
15914                   struct Row9.3;
15915                   struct Row10;"#},
15916        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15917        indoc! {r#"struct Row;
15918                   struct Row1;
15919                   struct Row1.1;
15920                   struct Row1.2;
15921                   struct Row2;ˇ
15922
15923                   struct Row4;
15924                   struct Row5;
15925                   struct Row6;
15926
15927                   struct Row8;
15928                   ˇstruct Row9;
15929                   struct Row9.1;
15930                   struct Row9.2;
15931                   struct Row9.3;
15932                   struct Row10;"#},
15933        base_text,
15934        &mut cx,
15935    );
15936    // Same for selections
15937    assert_hunk_revert(
15938        indoc! {r#"struct Row;
15939                   struct Row1;
15940                   struct Row2;
15941                   struct Row2.1;
15942                   struct Row2.2;
15943                   «ˇ
15944                   struct Row4;
15945                   struct» Row5;
15946                   «struct Row6;
15947                   ˇ»
15948                   struct Row9.1;
15949                   struct Row9.2;
15950                   struct Row9.3;
15951                   struct Row8;
15952                   struct Row9;
15953                   struct Row10;"#},
15954        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15955        indoc! {r#"struct Row;
15956                   struct Row1;
15957                   struct Row2;
15958                   struct Row2.1;
15959                   struct Row2.2;
15960                   «ˇ
15961                   struct Row4;
15962                   struct» Row5;
15963                   «struct Row6;
15964                   ˇ»
15965                   struct Row9.1;
15966                   struct Row9.2;
15967                   struct Row9.3;
15968                   struct Row8;
15969                   struct Row9;
15970                   struct Row10;"#},
15971        base_text,
15972        &mut cx,
15973    );
15974
15975    // When carets and selections intersect the addition hunks, those are reverted.
15976    // Adjacent carets got merged.
15977    assert_hunk_revert(
15978        indoc! {r#"struct Row;
15979                   ˇ// something on the top
15980                   struct Row1;
15981                   struct Row2;
15982                   struct Roˇw3.1;
15983                   struct Row2.2;
15984                   struct Row2.3;ˇ
15985
15986                   struct Row4;
15987                   struct ˇRow5.1;
15988                   struct Row5.2;
15989                   struct «Rowˇ»5.3;
15990                   struct Row5;
15991                   struct Row6;
15992                   ˇ
15993                   struct Row9.1;
15994                   struct «Rowˇ»9.2;
15995                   struct «ˇRow»9.3;
15996                   struct Row8;
15997                   struct Row9;
15998                   «ˇ// something on bottom»
15999                   struct Row10;"#},
16000        vec![
16001            DiffHunkStatusKind::Added,
16002            DiffHunkStatusKind::Added,
16003            DiffHunkStatusKind::Added,
16004            DiffHunkStatusKind::Added,
16005            DiffHunkStatusKind::Added,
16006        ],
16007        indoc! {r#"struct Row;
16008                   ˇstruct Row1;
16009                   struct Row2;
16010                   ˇ
16011                   struct Row4;
16012                   ˇstruct Row5;
16013                   struct Row6;
16014                   ˇ
16015                   ˇstruct Row8;
16016                   struct Row9;
16017                   ˇstruct Row10;"#},
16018        base_text,
16019        &mut cx,
16020    );
16021}
16022
16023#[gpui::test]
16024async fn test_modification_reverts(cx: &mut TestAppContext) {
16025    init_test(cx, |_| {});
16026    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16027    let base_text = indoc! {r#"
16028        struct Row;
16029        struct Row1;
16030        struct Row2;
16031
16032        struct Row4;
16033        struct Row5;
16034        struct Row6;
16035
16036        struct Row8;
16037        struct Row9;
16038        struct Row10;"#};
16039
16040    // Modification hunks behave the same as the addition ones.
16041    assert_hunk_revert(
16042        indoc! {r#"struct Row;
16043                   struct Row1;
16044                   struct Row33;
16045                   ˇ
16046                   struct Row4;
16047                   struct Row5;
16048                   struct Row6;
16049                   ˇ
16050                   struct Row99;
16051                   struct Row9;
16052                   struct Row10;"#},
16053        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16054        indoc! {r#"struct Row;
16055                   struct Row1;
16056                   struct Row33;
16057                   ˇ
16058                   struct Row4;
16059                   struct Row5;
16060                   struct Row6;
16061                   ˇ
16062                   struct Row99;
16063                   struct Row9;
16064                   struct Row10;"#},
16065        base_text,
16066        &mut cx,
16067    );
16068    assert_hunk_revert(
16069        indoc! {r#"struct Row;
16070                   struct Row1;
16071                   struct Row33;
16072                   «ˇ
16073                   struct Row4;
16074                   struct» Row5;
16075                   «struct Row6;
16076                   ˇ»
16077                   struct Row99;
16078                   struct Row9;
16079                   struct Row10;"#},
16080        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16081        indoc! {r#"struct Row;
16082                   struct Row1;
16083                   struct Row33;
16084                   «ˇ
16085                   struct Row4;
16086                   struct» Row5;
16087                   «struct Row6;
16088                   ˇ»
16089                   struct Row99;
16090                   struct Row9;
16091                   struct Row10;"#},
16092        base_text,
16093        &mut cx,
16094    );
16095
16096    assert_hunk_revert(
16097        indoc! {r#"ˇstruct Row1.1;
16098                   struct Row1;
16099                   «ˇstr»uct Row22;
16100
16101                   struct ˇRow44;
16102                   struct Row5;
16103                   struct «Rˇ»ow66;ˇ
16104
16105                   «struˇ»ct Row88;
16106                   struct Row9;
16107                   struct Row1011;ˇ"#},
16108        vec![
16109            DiffHunkStatusKind::Modified,
16110            DiffHunkStatusKind::Modified,
16111            DiffHunkStatusKind::Modified,
16112            DiffHunkStatusKind::Modified,
16113            DiffHunkStatusKind::Modified,
16114            DiffHunkStatusKind::Modified,
16115        ],
16116        indoc! {r#"struct Row;
16117                   ˇstruct Row1;
16118                   struct Row2;
16119                   ˇ
16120                   struct Row4;
16121                   ˇstruct Row5;
16122                   struct Row6;
16123                   ˇ
16124                   struct Row8;
16125                   ˇstruct Row9;
16126                   struct Row10;ˇ"#},
16127        base_text,
16128        &mut cx,
16129    );
16130}
16131
16132#[gpui::test]
16133async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16134    init_test(cx, |_| {});
16135    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16136    let base_text = indoc! {r#"
16137        one
16138
16139        two
16140        three
16141        "#};
16142
16143    cx.set_head_text(base_text);
16144    cx.set_state("\nˇ\n");
16145    cx.executor().run_until_parked();
16146    cx.update_editor(|editor, _window, cx| {
16147        editor.expand_selected_diff_hunks(cx);
16148    });
16149    cx.executor().run_until_parked();
16150    cx.update_editor(|editor, window, cx| {
16151        editor.backspace(&Default::default(), window, cx);
16152    });
16153    cx.run_until_parked();
16154    cx.assert_state_with_diff(
16155        indoc! {r#"
16156
16157        - two
16158        - threeˇ
16159        +
16160        "#}
16161        .to_string(),
16162    );
16163}
16164
16165#[gpui::test]
16166async fn test_deletion_reverts(cx: &mut TestAppContext) {
16167    init_test(cx, |_| {});
16168    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16169    let base_text = indoc! {r#"struct Row;
16170struct Row1;
16171struct Row2;
16172
16173struct Row4;
16174struct Row5;
16175struct Row6;
16176
16177struct Row8;
16178struct Row9;
16179struct Row10;"#};
16180
16181    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16182    assert_hunk_revert(
16183        indoc! {r#"struct Row;
16184                   struct Row2;
16185
16186                   ˇstruct Row4;
16187                   struct Row5;
16188                   struct Row6;
16189                   ˇ
16190                   struct Row8;
16191                   struct Row10;"#},
16192        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16193        indoc! {r#"struct Row;
16194                   struct Row2;
16195
16196                   ˇstruct Row4;
16197                   struct Row5;
16198                   struct Row6;
16199                   ˇ
16200                   struct Row8;
16201                   struct Row10;"#},
16202        base_text,
16203        &mut cx,
16204    );
16205    assert_hunk_revert(
16206        indoc! {r#"struct Row;
16207                   struct Row2;
16208
16209                   «ˇstruct Row4;
16210                   struct» Row5;
16211                   «struct Row6;
16212                   ˇ»
16213                   struct Row8;
16214                   struct Row10;"#},
16215        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16216        indoc! {r#"struct Row;
16217                   struct Row2;
16218
16219                   «ˇstruct Row4;
16220                   struct» Row5;
16221                   «struct Row6;
16222                   ˇ»
16223                   struct Row8;
16224                   struct Row10;"#},
16225        base_text,
16226        &mut cx,
16227    );
16228
16229    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16230    assert_hunk_revert(
16231        indoc! {r#"struct Row;
16232                   ˇstruct Row2;
16233
16234                   struct Row4;
16235                   struct Row5;
16236                   struct Row6;
16237
16238                   struct Row8;ˇ
16239                   struct Row10;"#},
16240        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16241        indoc! {r#"struct Row;
16242                   struct Row1;
16243                   ˇstruct Row2;
16244
16245                   struct Row4;
16246                   struct Row5;
16247                   struct Row6;
16248
16249                   struct Row8;ˇ
16250                   struct Row9;
16251                   struct Row10;"#},
16252        base_text,
16253        &mut cx,
16254    );
16255    assert_hunk_revert(
16256        indoc! {r#"struct Row;
16257                   struct Row2«ˇ;
16258                   struct Row4;
16259                   struct» Row5;
16260                   «struct Row6;
16261
16262                   struct Row8;ˇ»
16263                   struct Row10;"#},
16264        vec![
16265            DiffHunkStatusKind::Deleted,
16266            DiffHunkStatusKind::Deleted,
16267            DiffHunkStatusKind::Deleted,
16268        ],
16269        indoc! {r#"struct Row;
16270                   struct Row1;
16271                   struct Row2«ˇ;
16272
16273                   struct Row4;
16274                   struct» Row5;
16275                   «struct Row6;
16276
16277                   struct Row8;ˇ»
16278                   struct Row9;
16279                   struct Row10;"#},
16280        base_text,
16281        &mut cx,
16282    );
16283}
16284
16285#[gpui::test]
16286async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16287    init_test(cx, |_| {});
16288
16289    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16290    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16291    let base_text_3 =
16292        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16293
16294    let text_1 = edit_first_char_of_every_line(base_text_1);
16295    let text_2 = edit_first_char_of_every_line(base_text_2);
16296    let text_3 = edit_first_char_of_every_line(base_text_3);
16297
16298    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16299    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16300    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16301
16302    let multibuffer = cx.new(|cx| {
16303        let mut multibuffer = MultiBuffer::new(ReadWrite);
16304        multibuffer.push_excerpts(
16305            buffer_1.clone(),
16306            [
16307                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16308                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16309                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16310            ],
16311            cx,
16312        );
16313        multibuffer.push_excerpts(
16314            buffer_2.clone(),
16315            [
16316                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16317                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16318                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16319            ],
16320            cx,
16321        );
16322        multibuffer.push_excerpts(
16323            buffer_3.clone(),
16324            [
16325                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16326                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16327                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16328            ],
16329            cx,
16330        );
16331        multibuffer
16332    });
16333
16334    let fs = FakeFs::new(cx.executor());
16335    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16336    let (editor, cx) = cx
16337        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16338    editor.update_in(cx, |editor, _window, cx| {
16339        for (buffer, diff_base) in [
16340            (buffer_1.clone(), base_text_1),
16341            (buffer_2.clone(), base_text_2),
16342            (buffer_3.clone(), base_text_3),
16343        ] {
16344            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16345            editor
16346                .buffer
16347                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16348        }
16349    });
16350    cx.executor().run_until_parked();
16351
16352    editor.update_in(cx, |editor, window, cx| {
16353        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}");
16354        editor.select_all(&SelectAll, window, cx);
16355        editor.git_restore(&Default::default(), window, cx);
16356    });
16357    cx.executor().run_until_parked();
16358
16359    // When all ranges are selected, all buffer hunks are reverted.
16360    editor.update(cx, |editor, cx| {
16361        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");
16362    });
16363    buffer_1.update(cx, |buffer, _| {
16364        assert_eq!(buffer.text(), base_text_1);
16365    });
16366    buffer_2.update(cx, |buffer, _| {
16367        assert_eq!(buffer.text(), base_text_2);
16368    });
16369    buffer_3.update(cx, |buffer, _| {
16370        assert_eq!(buffer.text(), base_text_3);
16371    });
16372
16373    editor.update_in(cx, |editor, window, cx| {
16374        editor.undo(&Default::default(), window, cx);
16375    });
16376
16377    editor.update_in(cx, |editor, window, cx| {
16378        editor.change_selections(None, window, cx, |s| {
16379            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16380        });
16381        editor.git_restore(&Default::default(), window, cx);
16382    });
16383
16384    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16385    // but not affect buffer_2 and its related excerpts.
16386    editor.update(cx, |editor, cx| {
16387        assert_eq!(
16388            editor.text(cx),
16389            "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}"
16390        );
16391    });
16392    buffer_1.update(cx, |buffer, _| {
16393        assert_eq!(buffer.text(), base_text_1);
16394    });
16395    buffer_2.update(cx, |buffer, _| {
16396        assert_eq!(
16397            buffer.text(),
16398            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16399        );
16400    });
16401    buffer_3.update(cx, |buffer, _| {
16402        assert_eq!(
16403            buffer.text(),
16404            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16405        );
16406    });
16407
16408    fn edit_first_char_of_every_line(text: &str) -> String {
16409        text.split('\n')
16410            .map(|line| format!("X{}", &line[1..]))
16411            .collect::<Vec<_>>()
16412            .join("\n")
16413    }
16414}
16415
16416#[gpui::test]
16417async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16418    init_test(cx, |_| {});
16419
16420    let cols = 4;
16421    let rows = 10;
16422    let sample_text_1 = sample_text(rows, cols, 'a');
16423    assert_eq!(
16424        sample_text_1,
16425        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16426    );
16427    let sample_text_2 = sample_text(rows, cols, 'l');
16428    assert_eq!(
16429        sample_text_2,
16430        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16431    );
16432    let sample_text_3 = sample_text(rows, cols, 'v');
16433    assert_eq!(
16434        sample_text_3,
16435        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16436    );
16437
16438    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16439    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16440    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16441
16442    let multi_buffer = cx.new(|cx| {
16443        let mut multibuffer = MultiBuffer::new(ReadWrite);
16444        multibuffer.push_excerpts(
16445            buffer_1.clone(),
16446            [
16447                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16448                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16449                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16450            ],
16451            cx,
16452        );
16453        multibuffer.push_excerpts(
16454            buffer_2.clone(),
16455            [
16456                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16457                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16458                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16459            ],
16460            cx,
16461        );
16462        multibuffer.push_excerpts(
16463            buffer_3.clone(),
16464            [
16465                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16466                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16467                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16468            ],
16469            cx,
16470        );
16471        multibuffer
16472    });
16473
16474    let fs = FakeFs::new(cx.executor());
16475    fs.insert_tree(
16476        "/a",
16477        json!({
16478            "main.rs": sample_text_1,
16479            "other.rs": sample_text_2,
16480            "lib.rs": sample_text_3,
16481        }),
16482    )
16483    .await;
16484    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16485    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16486    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16487    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16488        Editor::new(
16489            EditorMode::full(),
16490            multi_buffer,
16491            Some(project.clone()),
16492            window,
16493            cx,
16494        )
16495    });
16496    let multibuffer_item_id = workspace
16497        .update(cx, |workspace, window, cx| {
16498            assert!(
16499                workspace.active_item(cx).is_none(),
16500                "active item should be None before the first item is added"
16501            );
16502            workspace.add_item_to_active_pane(
16503                Box::new(multi_buffer_editor.clone()),
16504                None,
16505                true,
16506                window,
16507                cx,
16508            );
16509            let active_item = workspace
16510                .active_item(cx)
16511                .expect("should have an active item after adding the multi buffer");
16512            assert!(
16513                !active_item.is_singleton(cx),
16514                "A multi buffer was expected to active after adding"
16515            );
16516            active_item.item_id()
16517        })
16518        .unwrap();
16519    cx.executor().run_until_parked();
16520
16521    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16522        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16523            s.select_ranges(Some(1..2))
16524        });
16525        editor.open_excerpts(&OpenExcerpts, window, cx);
16526    });
16527    cx.executor().run_until_parked();
16528    let first_item_id = workspace
16529        .update(cx, |workspace, window, cx| {
16530            let active_item = workspace
16531                .active_item(cx)
16532                .expect("should have an active item after navigating into the 1st buffer");
16533            let first_item_id = active_item.item_id();
16534            assert_ne!(
16535                first_item_id, multibuffer_item_id,
16536                "Should navigate into the 1st buffer and activate it"
16537            );
16538            assert!(
16539                active_item.is_singleton(cx),
16540                "New active item should be a singleton buffer"
16541            );
16542            assert_eq!(
16543                active_item
16544                    .act_as::<Editor>(cx)
16545                    .expect("should have navigated into an editor for the 1st buffer")
16546                    .read(cx)
16547                    .text(cx),
16548                sample_text_1
16549            );
16550
16551            workspace
16552                .go_back(workspace.active_pane().downgrade(), window, cx)
16553                .detach_and_log_err(cx);
16554
16555            first_item_id
16556        })
16557        .unwrap();
16558    cx.executor().run_until_parked();
16559    workspace
16560        .update(cx, |workspace, _, cx| {
16561            let active_item = workspace
16562                .active_item(cx)
16563                .expect("should have an active item after navigating back");
16564            assert_eq!(
16565                active_item.item_id(),
16566                multibuffer_item_id,
16567                "Should navigate back to the multi buffer"
16568            );
16569            assert!(!active_item.is_singleton(cx));
16570        })
16571        .unwrap();
16572
16573    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16574        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16575            s.select_ranges(Some(39..40))
16576        });
16577        editor.open_excerpts(&OpenExcerpts, window, cx);
16578    });
16579    cx.executor().run_until_parked();
16580    let second_item_id = workspace
16581        .update(cx, |workspace, window, cx| {
16582            let active_item = workspace
16583                .active_item(cx)
16584                .expect("should have an active item after navigating into the 2nd buffer");
16585            let second_item_id = active_item.item_id();
16586            assert_ne!(
16587                second_item_id, multibuffer_item_id,
16588                "Should navigate away from the multibuffer"
16589            );
16590            assert_ne!(
16591                second_item_id, first_item_id,
16592                "Should navigate into the 2nd buffer and activate it"
16593            );
16594            assert!(
16595                active_item.is_singleton(cx),
16596                "New active item should be a singleton buffer"
16597            );
16598            assert_eq!(
16599                active_item
16600                    .act_as::<Editor>(cx)
16601                    .expect("should have navigated into an editor")
16602                    .read(cx)
16603                    .text(cx),
16604                sample_text_2
16605            );
16606
16607            workspace
16608                .go_back(workspace.active_pane().downgrade(), window, cx)
16609                .detach_and_log_err(cx);
16610
16611            second_item_id
16612        })
16613        .unwrap();
16614    cx.executor().run_until_parked();
16615    workspace
16616        .update(cx, |workspace, _, cx| {
16617            let active_item = workspace
16618                .active_item(cx)
16619                .expect("should have an active item after navigating back from the 2nd buffer");
16620            assert_eq!(
16621                active_item.item_id(),
16622                multibuffer_item_id,
16623                "Should navigate back from the 2nd buffer to the multi buffer"
16624            );
16625            assert!(!active_item.is_singleton(cx));
16626        })
16627        .unwrap();
16628
16629    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16630        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16631            s.select_ranges(Some(70..70))
16632        });
16633        editor.open_excerpts(&OpenExcerpts, window, cx);
16634    });
16635    cx.executor().run_until_parked();
16636    workspace
16637        .update(cx, |workspace, window, cx| {
16638            let active_item = workspace
16639                .active_item(cx)
16640                .expect("should have an active item after navigating into the 3rd buffer");
16641            let third_item_id = active_item.item_id();
16642            assert_ne!(
16643                third_item_id, multibuffer_item_id,
16644                "Should navigate into the 3rd buffer and activate it"
16645            );
16646            assert_ne!(third_item_id, first_item_id);
16647            assert_ne!(third_item_id, second_item_id);
16648            assert!(
16649                active_item.is_singleton(cx),
16650                "New active item should be a singleton buffer"
16651            );
16652            assert_eq!(
16653                active_item
16654                    .act_as::<Editor>(cx)
16655                    .expect("should have navigated into an editor")
16656                    .read(cx)
16657                    .text(cx),
16658                sample_text_3
16659            );
16660
16661            workspace
16662                .go_back(workspace.active_pane().downgrade(), window, cx)
16663                .detach_and_log_err(cx);
16664        })
16665        .unwrap();
16666    cx.executor().run_until_parked();
16667    workspace
16668        .update(cx, |workspace, _, cx| {
16669            let active_item = workspace
16670                .active_item(cx)
16671                .expect("should have an active item after navigating back from the 3rd buffer");
16672            assert_eq!(
16673                active_item.item_id(),
16674                multibuffer_item_id,
16675                "Should navigate back from the 3rd buffer to the multi buffer"
16676            );
16677            assert!(!active_item.is_singleton(cx));
16678        })
16679        .unwrap();
16680}
16681
16682#[gpui::test]
16683async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16684    init_test(cx, |_| {});
16685
16686    let mut cx = EditorTestContext::new(cx).await;
16687
16688    let diff_base = r#"
16689        use some::mod;
16690
16691        const A: u32 = 42;
16692
16693        fn main() {
16694            println!("hello");
16695
16696            println!("world");
16697        }
16698        "#
16699    .unindent();
16700
16701    cx.set_state(
16702        &r#"
16703        use some::modified;
16704
16705        ˇ
16706        fn main() {
16707            println!("hello there");
16708
16709            println!("around the");
16710            println!("world");
16711        }
16712        "#
16713        .unindent(),
16714    );
16715
16716    cx.set_head_text(&diff_base);
16717    executor.run_until_parked();
16718
16719    cx.update_editor(|editor, window, cx| {
16720        editor.go_to_next_hunk(&GoToHunk, window, cx);
16721        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16722    });
16723    executor.run_until_parked();
16724    cx.assert_state_with_diff(
16725        r#"
16726          use some::modified;
16727
16728
16729          fn main() {
16730        -     println!("hello");
16731        + ˇ    println!("hello there");
16732
16733              println!("around the");
16734              println!("world");
16735          }
16736        "#
16737        .unindent(),
16738    );
16739
16740    cx.update_editor(|editor, window, cx| {
16741        for _ in 0..2 {
16742            editor.go_to_next_hunk(&GoToHunk, window, cx);
16743            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16744        }
16745    });
16746    executor.run_until_parked();
16747    cx.assert_state_with_diff(
16748        r#"
16749        - use some::mod;
16750        + ˇuse some::modified;
16751
16752
16753          fn main() {
16754        -     println!("hello");
16755        +     println!("hello there");
16756
16757        +     println!("around the");
16758              println!("world");
16759          }
16760        "#
16761        .unindent(),
16762    );
16763
16764    cx.update_editor(|editor, window, cx| {
16765        editor.go_to_next_hunk(&GoToHunk, window, cx);
16766        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16767    });
16768    executor.run_until_parked();
16769    cx.assert_state_with_diff(
16770        r#"
16771        - use some::mod;
16772        + use some::modified;
16773
16774        - const A: u32 = 42;
16775          ˇ
16776          fn main() {
16777        -     println!("hello");
16778        +     println!("hello there");
16779
16780        +     println!("around the");
16781              println!("world");
16782          }
16783        "#
16784        .unindent(),
16785    );
16786
16787    cx.update_editor(|editor, window, cx| {
16788        editor.cancel(&Cancel, window, cx);
16789    });
16790
16791    cx.assert_state_with_diff(
16792        r#"
16793          use some::modified;
16794
16795          ˇ
16796          fn main() {
16797              println!("hello there");
16798
16799              println!("around the");
16800              println!("world");
16801          }
16802        "#
16803        .unindent(),
16804    );
16805}
16806
16807#[gpui::test]
16808async fn test_diff_base_change_with_expanded_diff_hunks(
16809    executor: BackgroundExecutor,
16810    cx: &mut TestAppContext,
16811) {
16812    init_test(cx, |_| {});
16813
16814    let mut cx = EditorTestContext::new(cx).await;
16815
16816    let diff_base = r#"
16817        use some::mod1;
16818        use some::mod2;
16819
16820        const A: u32 = 42;
16821        const B: u32 = 42;
16822        const C: u32 = 42;
16823
16824        fn main() {
16825            println!("hello");
16826
16827            println!("world");
16828        }
16829        "#
16830    .unindent();
16831
16832    cx.set_state(
16833        &r#"
16834        use some::mod2;
16835
16836        const A: u32 = 42;
16837        const C: u32 = 42;
16838
16839        fn main(ˇ) {
16840            //println!("hello");
16841
16842            println!("world");
16843            //
16844            //
16845        }
16846        "#
16847        .unindent(),
16848    );
16849
16850    cx.set_head_text(&diff_base);
16851    executor.run_until_parked();
16852
16853    cx.update_editor(|editor, window, cx| {
16854        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16855    });
16856    executor.run_until_parked();
16857    cx.assert_state_with_diff(
16858        r#"
16859        - use some::mod1;
16860          use some::mod2;
16861
16862          const A: u32 = 42;
16863        - const B: u32 = 42;
16864          const C: u32 = 42;
16865
16866          fn main(ˇ) {
16867        -     println!("hello");
16868        +     //println!("hello");
16869
16870              println!("world");
16871        +     //
16872        +     //
16873          }
16874        "#
16875        .unindent(),
16876    );
16877
16878    cx.set_head_text("new diff base!");
16879    executor.run_until_parked();
16880    cx.assert_state_with_diff(
16881        r#"
16882        - new diff base!
16883        + use some::mod2;
16884        +
16885        + const A: u32 = 42;
16886        + const C: u32 = 42;
16887        +
16888        + fn main(ˇ) {
16889        +     //println!("hello");
16890        +
16891        +     println!("world");
16892        +     //
16893        +     //
16894        + }
16895        "#
16896        .unindent(),
16897    );
16898}
16899
16900#[gpui::test]
16901async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16902    init_test(cx, |_| {});
16903
16904    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16905    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16906    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16907    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16908    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16909    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16910
16911    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16912    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16913    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16914
16915    let multi_buffer = cx.new(|cx| {
16916        let mut multibuffer = MultiBuffer::new(ReadWrite);
16917        multibuffer.push_excerpts(
16918            buffer_1.clone(),
16919            [
16920                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16921                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16922                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16923            ],
16924            cx,
16925        );
16926        multibuffer.push_excerpts(
16927            buffer_2.clone(),
16928            [
16929                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16930                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16931                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16932            ],
16933            cx,
16934        );
16935        multibuffer.push_excerpts(
16936            buffer_3.clone(),
16937            [
16938                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16939                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16940                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16941            ],
16942            cx,
16943        );
16944        multibuffer
16945    });
16946
16947    let editor =
16948        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16949    editor
16950        .update(cx, |editor, _window, cx| {
16951            for (buffer, diff_base) in [
16952                (buffer_1.clone(), file_1_old),
16953                (buffer_2.clone(), file_2_old),
16954                (buffer_3.clone(), file_3_old),
16955            ] {
16956                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16957                editor
16958                    .buffer
16959                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16960            }
16961        })
16962        .unwrap();
16963
16964    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16965    cx.run_until_parked();
16966
16967    cx.assert_editor_state(
16968        &"
16969            ˇaaa
16970            ccc
16971            ddd
16972
16973            ggg
16974            hhh
16975
16976
16977            lll
16978            mmm
16979            NNN
16980
16981            qqq
16982            rrr
16983
16984            uuu
16985            111
16986            222
16987            333
16988
16989            666
16990            777
16991
16992            000
16993            !!!"
16994        .unindent(),
16995    );
16996
16997    cx.update_editor(|editor, window, cx| {
16998        editor.select_all(&SelectAll, window, cx);
16999        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17000    });
17001    cx.executor().run_until_parked();
17002
17003    cx.assert_state_with_diff(
17004        "
17005            «aaa
17006          - bbb
17007            ccc
17008            ddd
17009
17010            ggg
17011            hhh
17012
17013
17014            lll
17015            mmm
17016          - nnn
17017          + NNN
17018
17019            qqq
17020            rrr
17021
17022            uuu
17023            111
17024            222
17025            333
17026
17027          + 666
17028            777
17029
17030            000
17031            !!!ˇ»"
17032            .unindent(),
17033    );
17034}
17035
17036#[gpui::test]
17037async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17038    init_test(cx, |_| {});
17039
17040    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17041    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17042
17043    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17044    let multi_buffer = cx.new(|cx| {
17045        let mut multibuffer = MultiBuffer::new(ReadWrite);
17046        multibuffer.push_excerpts(
17047            buffer.clone(),
17048            [
17049                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17050                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17051                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17052            ],
17053            cx,
17054        );
17055        multibuffer
17056    });
17057
17058    let editor =
17059        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17060    editor
17061        .update(cx, |editor, _window, cx| {
17062            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17063            editor
17064                .buffer
17065                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17066        })
17067        .unwrap();
17068
17069    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17070    cx.run_until_parked();
17071
17072    cx.update_editor(|editor, window, cx| {
17073        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17074    });
17075    cx.executor().run_until_parked();
17076
17077    // When the start of a hunk coincides with the start of its excerpt,
17078    // the hunk is expanded. When the start of a a hunk is earlier than
17079    // the start of its excerpt, the hunk is not expanded.
17080    cx.assert_state_with_diff(
17081        "
17082            ˇaaa
17083          - bbb
17084          + BBB
17085
17086          - ddd
17087          - eee
17088          + DDD
17089          + EEE
17090            fff
17091
17092            iii
17093        "
17094        .unindent(),
17095    );
17096}
17097
17098#[gpui::test]
17099async fn test_edits_around_expanded_insertion_hunks(
17100    executor: BackgroundExecutor,
17101    cx: &mut TestAppContext,
17102) {
17103    init_test(cx, |_| {});
17104
17105    let mut cx = EditorTestContext::new(cx).await;
17106
17107    let diff_base = r#"
17108        use some::mod1;
17109        use some::mod2;
17110
17111        const A: u32 = 42;
17112
17113        fn main() {
17114            println!("hello");
17115
17116            println!("world");
17117        }
17118        "#
17119    .unindent();
17120    executor.run_until_parked();
17121    cx.set_state(
17122        &r#"
17123        use some::mod1;
17124        use some::mod2;
17125
17126        const A: u32 = 42;
17127        const B: u32 = 42;
17128        const C: u32 = 42;
17129        ˇ
17130
17131        fn main() {
17132            println!("hello");
17133
17134            println!("world");
17135        }
17136        "#
17137        .unindent(),
17138    );
17139
17140    cx.set_head_text(&diff_base);
17141    executor.run_until_parked();
17142
17143    cx.update_editor(|editor, window, cx| {
17144        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17145    });
17146    executor.run_until_parked();
17147
17148    cx.assert_state_with_diff(
17149        r#"
17150        use some::mod1;
17151        use some::mod2;
17152
17153        const A: u32 = 42;
17154      + const B: u32 = 42;
17155      + const C: u32 = 42;
17156      + ˇ
17157
17158        fn main() {
17159            println!("hello");
17160
17161            println!("world");
17162        }
17163      "#
17164        .unindent(),
17165    );
17166
17167    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17168    executor.run_until_parked();
17169
17170    cx.assert_state_with_diff(
17171        r#"
17172        use some::mod1;
17173        use some::mod2;
17174
17175        const A: u32 = 42;
17176      + const B: u32 = 42;
17177      + const C: u32 = 42;
17178      + const D: u32 = 42;
17179      + ˇ
17180
17181        fn main() {
17182            println!("hello");
17183
17184            println!("world");
17185        }
17186      "#
17187        .unindent(),
17188    );
17189
17190    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17191    executor.run_until_parked();
17192
17193    cx.assert_state_with_diff(
17194        r#"
17195        use some::mod1;
17196        use some::mod2;
17197
17198        const A: u32 = 42;
17199      + const B: u32 = 42;
17200      + const C: u32 = 42;
17201      + const D: u32 = 42;
17202      + const E: u32 = 42;
17203      + ˇ
17204
17205        fn main() {
17206            println!("hello");
17207
17208            println!("world");
17209        }
17210      "#
17211        .unindent(),
17212    );
17213
17214    cx.update_editor(|editor, window, cx| {
17215        editor.delete_line(&DeleteLine, window, cx);
17216    });
17217    executor.run_until_parked();
17218
17219    cx.assert_state_with_diff(
17220        r#"
17221        use some::mod1;
17222        use some::mod2;
17223
17224        const A: u32 = 42;
17225      + const B: u32 = 42;
17226      + const C: u32 = 42;
17227      + const D: u32 = 42;
17228      + const E: u32 = 42;
17229        ˇ
17230        fn main() {
17231            println!("hello");
17232
17233            println!("world");
17234        }
17235      "#
17236        .unindent(),
17237    );
17238
17239    cx.update_editor(|editor, window, cx| {
17240        editor.move_up(&MoveUp, window, cx);
17241        editor.delete_line(&DeleteLine, window, cx);
17242        editor.move_up(&MoveUp, window, cx);
17243        editor.delete_line(&DeleteLine, window, cx);
17244        editor.move_up(&MoveUp, window, cx);
17245        editor.delete_line(&DeleteLine, window, cx);
17246    });
17247    executor.run_until_parked();
17248    cx.assert_state_with_diff(
17249        r#"
17250        use some::mod1;
17251        use some::mod2;
17252
17253        const A: u32 = 42;
17254      + const B: u32 = 42;
17255        ˇ
17256        fn main() {
17257            println!("hello");
17258
17259            println!("world");
17260        }
17261      "#
17262        .unindent(),
17263    );
17264
17265    cx.update_editor(|editor, window, cx| {
17266        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17267        editor.delete_line(&DeleteLine, window, cx);
17268    });
17269    executor.run_until_parked();
17270    cx.assert_state_with_diff(
17271        r#"
17272        ˇ
17273        fn main() {
17274            println!("hello");
17275
17276            println!("world");
17277        }
17278      "#
17279        .unindent(),
17280    );
17281}
17282
17283#[gpui::test]
17284async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17285    init_test(cx, |_| {});
17286
17287    let mut cx = EditorTestContext::new(cx).await;
17288    cx.set_head_text(indoc! { "
17289        one
17290        two
17291        three
17292        four
17293        five
17294        "
17295    });
17296    cx.set_state(indoc! { "
17297        one
17298        ˇthree
17299        five
17300    "});
17301    cx.run_until_parked();
17302    cx.update_editor(|editor, window, cx| {
17303        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17304    });
17305    cx.assert_state_with_diff(
17306        indoc! { "
17307        one
17308      - two
17309        ˇthree
17310      - four
17311        five
17312    "}
17313        .to_string(),
17314    );
17315    cx.update_editor(|editor, window, cx| {
17316        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17317    });
17318
17319    cx.assert_state_with_diff(
17320        indoc! { "
17321        one
17322        ˇthree
17323        five
17324    "}
17325        .to_string(),
17326    );
17327
17328    cx.set_state(indoc! { "
17329        one
17330        ˇTWO
17331        three
17332        four
17333        five
17334    "});
17335    cx.run_until_parked();
17336    cx.update_editor(|editor, window, cx| {
17337        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17338    });
17339
17340    cx.assert_state_with_diff(
17341        indoc! { "
17342            one
17343          - two
17344          + ˇTWO
17345            three
17346            four
17347            five
17348        "}
17349        .to_string(),
17350    );
17351    cx.update_editor(|editor, window, cx| {
17352        editor.move_up(&Default::default(), window, cx);
17353        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17354    });
17355    cx.assert_state_with_diff(
17356        indoc! { "
17357            one
17358            ˇTWO
17359            three
17360            four
17361            five
17362        "}
17363        .to_string(),
17364    );
17365}
17366
17367#[gpui::test]
17368async fn test_edits_around_expanded_deletion_hunks(
17369    executor: BackgroundExecutor,
17370    cx: &mut TestAppContext,
17371) {
17372    init_test(cx, |_| {});
17373
17374    let mut cx = EditorTestContext::new(cx).await;
17375
17376    let diff_base = r#"
17377        use some::mod1;
17378        use some::mod2;
17379
17380        const A: u32 = 42;
17381        const B: u32 = 42;
17382        const C: u32 = 42;
17383
17384
17385        fn main() {
17386            println!("hello");
17387
17388            println!("world");
17389        }
17390    "#
17391    .unindent();
17392    executor.run_until_parked();
17393    cx.set_state(
17394        &r#"
17395        use some::mod1;
17396        use some::mod2;
17397
17398        ˇconst B: u32 = 42;
17399        const C: u32 = 42;
17400
17401
17402        fn main() {
17403            println!("hello");
17404
17405            println!("world");
17406        }
17407        "#
17408        .unindent(),
17409    );
17410
17411    cx.set_head_text(&diff_base);
17412    executor.run_until_parked();
17413
17414    cx.update_editor(|editor, window, cx| {
17415        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17416    });
17417    executor.run_until_parked();
17418
17419    cx.assert_state_with_diff(
17420        r#"
17421        use some::mod1;
17422        use some::mod2;
17423
17424      - const A: u32 = 42;
17425        ˇconst B: u32 = 42;
17426        const C: u32 = 42;
17427
17428
17429        fn main() {
17430            println!("hello");
17431
17432            println!("world");
17433        }
17434      "#
17435        .unindent(),
17436    );
17437
17438    cx.update_editor(|editor, window, cx| {
17439        editor.delete_line(&DeleteLine, window, cx);
17440    });
17441    executor.run_until_parked();
17442    cx.assert_state_with_diff(
17443        r#"
17444        use some::mod1;
17445        use some::mod2;
17446
17447      - const A: u32 = 42;
17448      - const B: u32 = 42;
17449        ˇconst C: u32 = 42;
17450
17451
17452        fn main() {
17453            println!("hello");
17454
17455            println!("world");
17456        }
17457      "#
17458        .unindent(),
17459    );
17460
17461    cx.update_editor(|editor, window, cx| {
17462        editor.delete_line(&DeleteLine, window, cx);
17463    });
17464    executor.run_until_parked();
17465    cx.assert_state_with_diff(
17466        r#"
17467        use some::mod1;
17468        use some::mod2;
17469
17470      - const A: u32 = 42;
17471      - const B: u32 = 42;
17472      - const C: u32 = 42;
17473        ˇ
17474
17475        fn main() {
17476            println!("hello");
17477
17478            println!("world");
17479        }
17480      "#
17481        .unindent(),
17482    );
17483
17484    cx.update_editor(|editor, window, cx| {
17485        editor.handle_input("replacement", window, cx);
17486    });
17487    executor.run_until_parked();
17488    cx.assert_state_with_diff(
17489        r#"
17490        use some::mod1;
17491        use some::mod2;
17492
17493      - const A: u32 = 42;
17494      - const B: u32 = 42;
17495      - const C: u32 = 42;
17496      -
17497      + replacementˇ
17498
17499        fn main() {
17500            println!("hello");
17501
17502            println!("world");
17503        }
17504      "#
17505        .unindent(),
17506    );
17507}
17508
17509#[gpui::test]
17510async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17511    init_test(cx, |_| {});
17512
17513    let mut cx = EditorTestContext::new(cx).await;
17514
17515    let base_text = r#"
17516        one
17517        two
17518        three
17519        four
17520        five
17521    "#
17522    .unindent();
17523    executor.run_until_parked();
17524    cx.set_state(
17525        &r#"
17526        one
17527        two
17528        fˇour
17529        five
17530        "#
17531        .unindent(),
17532    );
17533
17534    cx.set_head_text(&base_text);
17535    executor.run_until_parked();
17536
17537    cx.update_editor(|editor, window, cx| {
17538        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17539    });
17540    executor.run_until_parked();
17541
17542    cx.assert_state_with_diff(
17543        r#"
17544          one
17545          two
17546        - three
17547          fˇour
17548          five
17549        "#
17550        .unindent(),
17551    );
17552
17553    cx.update_editor(|editor, window, cx| {
17554        editor.backspace(&Backspace, window, cx);
17555        editor.backspace(&Backspace, window, cx);
17556    });
17557    executor.run_until_parked();
17558    cx.assert_state_with_diff(
17559        r#"
17560          one
17561          two
17562        - threeˇ
17563        - four
17564        + our
17565          five
17566        "#
17567        .unindent(),
17568    );
17569}
17570
17571#[gpui::test]
17572async fn test_edit_after_expanded_modification_hunk(
17573    executor: BackgroundExecutor,
17574    cx: &mut TestAppContext,
17575) {
17576    init_test(cx, |_| {});
17577
17578    let mut cx = EditorTestContext::new(cx).await;
17579
17580    let diff_base = r#"
17581        use some::mod1;
17582        use some::mod2;
17583
17584        const A: u32 = 42;
17585        const B: u32 = 42;
17586        const C: u32 = 42;
17587        const D: u32 = 42;
17588
17589
17590        fn main() {
17591            println!("hello");
17592
17593            println!("world");
17594        }"#
17595    .unindent();
17596
17597    cx.set_state(
17598        &r#"
17599        use some::mod1;
17600        use some::mod2;
17601
17602        const A: u32 = 42;
17603        const B: u32 = 42;
17604        const C: u32 = 43ˇ
17605        const D: u32 = 42;
17606
17607
17608        fn main() {
17609            println!("hello");
17610
17611            println!("world");
17612        }"#
17613        .unindent(),
17614    );
17615
17616    cx.set_head_text(&diff_base);
17617    executor.run_until_parked();
17618    cx.update_editor(|editor, window, cx| {
17619        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17620    });
17621    executor.run_until_parked();
17622
17623    cx.assert_state_with_diff(
17624        r#"
17625        use some::mod1;
17626        use some::mod2;
17627
17628        const A: u32 = 42;
17629        const B: u32 = 42;
17630      - const C: u32 = 42;
17631      + const C: u32 = 43ˇ
17632        const D: u32 = 42;
17633
17634
17635        fn main() {
17636            println!("hello");
17637
17638            println!("world");
17639        }"#
17640        .unindent(),
17641    );
17642
17643    cx.update_editor(|editor, window, cx| {
17644        editor.handle_input("\nnew_line\n", window, cx);
17645    });
17646    executor.run_until_parked();
17647
17648    cx.assert_state_with_diff(
17649        r#"
17650        use some::mod1;
17651        use some::mod2;
17652
17653        const A: u32 = 42;
17654        const B: u32 = 42;
17655      - const C: u32 = 42;
17656      + const C: u32 = 43
17657      + new_line
17658      + ˇ
17659        const D: u32 = 42;
17660
17661
17662        fn main() {
17663            println!("hello");
17664
17665            println!("world");
17666        }"#
17667        .unindent(),
17668    );
17669}
17670
17671#[gpui::test]
17672async fn test_stage_and_unstage_added_file_hunk(
17673    executor: BackgroundExecutor,
17674    cx: &mut TestAppContext,
17675) {
17676    init_test(cx, |_| {});
17677
17678    let mut cx = EditorTestContext::new(cx).await;
17679    cx.update_editor(|editor, _, cx| {
17680        editor.set_expand_all_diff_hunks(cx);
17681    });
17682
17683    let working_copy = r#"
17684            ˇfn main() {
17685                println!("hello, world!");
17686            }
17687        "#
17688    .unindent();
17689
17690    cx.set_state(&working_copy);
17691    executor.run_until_parked();
17692
17693    cx.assert_state_with_diff(
17694        r#"
17695            + ˇfn main() {
17696            +     println!("hello, world!");
17697            + }
17698        "#
17699        .unindent(),
17700    );
17701    cx.assert_index_text(None);
17702
17703    cx.update_editor(|editor, window, cx| {
17704        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17705    });
17706    executor.run_until_parked();
17707    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17708    cx.assert_state_with_diff(
17709        r#"
17710            + ˇfn main() {
17711            +     println!("hello, world!");
17712            + }
17713        "#
17714        .unindent(),
17715    );
17716
17717    cx.update_editor(|editor, window, cx| {
17718        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17719    });
17720    executor.run_until_parked();
17721    cx.assert_index_text(None);
17722}
17723
17724async fn setup_indent_guides_editor(
17725    text: &str,
17726    cx: &mut TestAppContext,
17727) -> (BufferId, EditorTestContext) {
17728    init_test(cx, |_| {});
17729
17730    let mut cx = EditorTestContext::new(cx).await;
17731
17732    let buffer_id = cx.update_editor(|editor, window, cx| {
17733        editor.set_text(text, window, cx);
17734        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17735
17736        buffer_ids[0]
17737    });
17738
17739    (buffer_id, cx)
17740}
17741
17742fn assert_indent_guides(
17743    range: Range<u32>,
17744    expected: Vec<IndentGuide>,
17745    active_indices: Option<Vec<usize>>,
17746    cx: &mut EditorTestContext,
17747) {
17748    let indent_guides = cx.update_editor(|editor, window, cx| {
17749        let snapshot = editor.snapshot(window, cx).display_snapshot;
17750        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17751            editor,
17752            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17753            true,
17754            &snapshot,
17755            cx,
17756        );
17757
17758        indent_guides.sort_by(|a, b| {
17759            a.depth.cmp(&b.depth).then(
17760                a.start_row
17761                    .cmp(&b.start_row)
17762                    .then(a.end_row.cmp(&b.end_row)),
17763            )
17764        });
17765        indent_guides
17766    });
17767
17768    if let Some(expected) = active_indices {
17769        let active_indices = cx.update_editor(|editor, window, cx| {
17770            let snapshot = editor.snapshot(window, cx).display_snapshot;
17771            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17772        });
17773
17774        assert_eq!(
17775            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17776            expected,
17777            "Active indent guide indices do not match"
17778        );
17779    }
17780
17781    assert_eq!(indent_guides, expected, "Indent guides do not match");
17782}
17783
17784fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17785    IndentGuide {
17786        buffer_id,
17787        start_row: MultiBufferRow(start_row),
17788        end_row: MultiBufferRow(end_row),
17789        depth,
17790        tab_size: 4,
17791        settings: IndentGuideSettings {
17792            enabled: true,
17793            line_width: 1,
17794            active_line_width: 1,
17795            ..Default::default()
17796        },
17797    }
17798}
17799
17800#[gpui::test]
17801async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17802    let (buffer_id, mut cx) = setup_indent_guides_editor(
17803        &"
17804        fn main() {
17805            let a = 1;
17806        }"
17807        .unindent(),
17808        cx,
17809    )
17810    .await;
17811
17812    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17813}
17814
17815#[gpui::test]
17816async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17817    let (buffer_id, mut cx) = setup_indent_guides_editor(
17818        &"
17819        fn main() {
17820            let a = 1;
17821            let b = 2;
17822        }"
17823        .unindent(),
17824        cx,
17825    )
17826    .await;
17827
17828    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17829}
17830
17831#[gpui::test]
17832async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17833    let (buffer_id, mut cx) = setup_indent_guides_editor(
17834        &"
17835        fn main() {
17836            let a = 1;
17837            if a == 3 {
17838                let b = 2;
17839            } else {
17840                let c = 3;
17841            }
17842        }"
17843        .unindent(),
17844        cx,
17845    )
17846    .await;
17847
17848    assert_indent_guides(
17849        0..8,
17850        vec![
17851            indent_guide(buffer_id, 1, 6, 0),
17852            indent_guide(buffer_id, 3, 3, 1),
17853            indent_guide(buffer_id, 5, 5, 1),
17854        ],
17855        None,
17856        &mut cx,
17857    );
17858}
17859
17860#[gpui::test]
17861async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17862    let (buffer_id, mut cx) = setup_indent_guides_editor(
17863        &"
17864        fn main() {
17865            let a = 1;
17866                let b = 2;
17867            let c = 3;
17868        }"
17869        .unindent(),
17870        cx,
17871    )
17872    .await;
17873
17874    assert_indent_guides(
17875        0..5,
17876        vec![
17877            indent_guide(buffer_id, 1, 3, 0),
17878            indent_guide(buffer_id, 2, 2, 1),
17879        ],
17880        None,
17881        &mut cx,
17882    );
17883}
17884
17885#[gpui::test]
17886async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17887    let (buffer_id, mut cx) = setup_indent_guides_editor(
17888        &"
17889        fn main() {
17890            let a = 1;
17891
17892            let c = 3;
17893        }"
17894        .unindent(),
17895        cx,
17896    )
17897    .await;
17898
17899    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17900}
17901
17902#[gpui::test]
17903async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17904    let (buffer_id, mut cx) = setup_indent_guides_editor(
17905        &"
17906        fn main() {
17907            let a = 1;
17908
17909            let c = 3;
17910
17911            if a == 3 {
17912                let b = 2;
17913            } else {
17914                let c = 3;
17915            }
17916        }"
17917        .unindent(),
17918        cx,
17919    )
17920    .await;
17921
17922    assert_indent_guides(
17923        0..11,
17924        vec![
17925            indent_guide(buffer_id, 1, 9, 0),
17926            indent_guide(buffer_id, 6, 6, 1),
17927            indent_guide(buffer_id, 8, 8, 1),
17928        ],
17929        None,
17930        &mut cx,
17931    );
17932}
17933
17934#[gpui::test]
17935async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17936    let (buffer_id, mut cx) = setup_indent_guides_editor(
17937        &"
17938        fn main() {
17939            let a = 1;
17940
17941            let c = 3;
17942
17943            if a == 3 {
17944                let b = 2;
17945            } else {
17946                let c = 3;
17947            }
17948        }"
17949        .unindent(),
17950        cx,
17951    )
17952    .await;
17953
17954    assert_indent_guides(
17955        1..11,
17956        vec![
17957            indent_guide(buffer_id, 1, 9, 0),
17958            indent_guide(buffer_id, 6, 6, 1),
17959            indent_guide(buffer_id, 8, 8, 1),
17960        ],
17961        None,
17962        &mut cx,
17963    );
17964}
17965
17966#[gpui::test]
17967async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17968    let (buffer_id, mut cx) = setup_indent_guides_editor(
17969        &"
17970        fn main() {
17971            let a = 1;
17972
17973            let c = 3;
17974
17975            if a == 3 {
17976                let b = 2;
17977            } else {
17978                let c = 3;
17979            }
17980        }"
17981        .unindent(),
17982        cx,
17983    )
17984    .await;
17985
17986    assert_indent_guides(
17987        1..10,
17988        vec![
17989            indent_guide(buffer_id, 1, 9, 0),
17990            indent_guide(buffer_id, 6, 6, 1),
17991            indent_guide(buffer_id, 8, 8, 1),
17992        ],
17993        None,
17994        &mut cx,
17995    );
17996}
17997
17998#[gpui::test]
17999async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18000    let (buffer_id, mut cx) = setup_indent_guides_editor(
18001        &"
18002        fn main() {
18003            if a {
18004                b(
18005                    c,
18006                    d,
18007                )
18008            } else {
18009                e(
18010                    f
18011                )
18012            }
18013        }"
18014        .unindent(),
18015        cx,
18016    )
18017    .await;
18018
18019    assert_indent_guides(
18020        0..11,
18021        vec![
18022            indent_guide(buffer_id, 1, 10, 0),
18023            indent_guide(buffer_id, 2, 5, 1),
18024            indent_guide(buffer_id, 7, 9, 1),
18025            indent_guide(buffer_id, 3, 4, 2),
18026            indent_guide(buffer_id, 8, 8, 2),
18027        ],
18028        None,
18029        &mut cx,
18030    );
18031
18032    cx.update_editor(|editor, window, cx| {
18033        editor.fold_at(MultiBufferRow(2), window, cx);
18034        assert_eq!(
18035            editor.display_text(cx),
18036            "
18037            fn main() {
18038                if a {
18039                    b(⋯
18040                    )
18041                } else {
18042                    e(
18043                        f
18044                    )
18045                }
18046            }"
18047            .unindent()
18048        );
18049    });
18050
18051    assert_indent_guides(
18052        0..11,
18053        vec![
18054            indent_guide(buffer_id, 1, 10, 0),
18055            indent_guide(buffer_id, 2, 5, 1),
18056            indent_guide(buffer_id, 7, 9, 1),
18057            indent_guide(buffer_id, 8, 8, 2),
18058        ],
18059        None,
18060        &mut cx,
18061    );
18062}
18063
18064#[gpui::test]
18065async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18066    let (buffer_id, mut cx) = setup_indent_guides_editor(
18067        &"
18068        block1
18069            block2
18070                block3
18071                    block4
18072            block2
18073        block1
18074        block1"
18075            .unindent(),
18076        cx,
18077    )
18078    .await;
18079
18080    assert_indent_guides(
18081        1..10,
18082        vec![
18083            indent_guide(buffer_id, 1, 4, 0),
18084            indent_guide(buffer_id, 2, 3, 1),
18085            indent_guide(buffer_id, 3, 3, 2),
18086        ],
18087        None,
18088        &mut cx,
18089    );
18090}
18091
18092#[gpui::test]
18093async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18094    let (buffer_id, mut cx) = setup_indent_guides_editor(
18095        &"
18096        block1
18097            block2
18098                block3
18099
18100        block1
18101        block1"
18102            .unindent(),
18103        cx,
18104    )
18105    .await;
18106
18107    assert_indent_guides(
18108        0..6,
18109        vec![
18110            indent_guide(buffer_id, 1, 2, 0),
18111            indent_guide(buffer_id, 2, 2, 1),
18112        ],
18113        None,
18114        &mut cx,
18115    );
18116}
18117
18118#[gpui::test]
18119async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18120    let (buffer_id, mut cx) = setup_indent_guides_editor(
18121        &"
18122        function component() {
18123        \treturn (
18124        \t\t\t
18125        \t\t<div>
18126        \t\t\t<abc></abc>
18127        \t\t</div>
18128        \t)
18129        }"
18130        .unindent(),
18131        cx,
18132    )
18133    .await;
18134
18135    assert_indent_guides(
18136        0..8,
18137        vec![
18138            indent_guide(buffer_id, 1, 6, 0),
18139            indent_guide(buffer_id, 2, 5, 1),
18140            indent_guide(buffer_id, 4, 4, 2),
18141        ],
18142        None,
18143        &mut cx,
18144    );
18145}
18146
18147#[gpui::test]
18148async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18149    let (buffer_id, mut cx) = setup_indent_guides_editor(
18150        &"
18151        function component() {
18152        \treturn (
18153        \t
18154        \t\t<div>
18155        \t\t\t<abc></abc>
18156        \t\t</div>
18157        \t)
18158        }"
18159        .unindent(),
18160        cx,
18161    )
18162    .await;
18163
18164    assert_indent_guides(
18165        0..8,
18166        vec![
18167            indent_guide(buffer_id, 1, 6, 0),
18168            indent_guide(buffer_id, 2, 5, 1),
18169            indent_guide(buffer_id, 4, 4, 2),
18170        ],
18171        None,
18172        &mut cx,
18173    );
18174}
18175
18176#[gpui::test]
18177async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18178    let (buffer_id, mut cx) = setup_indent_guides_editor(
18179        &"
18180        block1
18181
18182
18183
18184            block2
18185        "
18186        .unindent(),
18187        cx,
18188    )
18189    .await;
18190
18191    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18192}
18193
18194#[gpui::test]
18195async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18196    let (buffer_id, mut cx) = setup_indent_guides_editor(
18197        &"
18198        def a:
18199        \tb = 3
18200        \tif True:
18201        \t\tc = 4
18202        \t\td = 5
18203        \tprint(b)
18204        "
18205        .unindent(),
18206        cx,
18207    )
18208    .await;
18209
18210    assert_indent_guides(
18211        0..6,
18212        vec![
18213            indent_guide(buffer_id, 1, 5, 0),
18214            indent_guide(buffer_id, 3, 4, 1),
18215        ],
18216        None,
18217        &mut cx,
18218    );
18219}
18220
18221#[gpui::test]
18222async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18223    let (buffer_id, mut cx) = setup_indent_guides_editor(
18224        &"
18225    fn main() {
18226        let a = 1;
18227    }"
18228        .unindent(),
18229        cx,
18230    )
18231    .await;
18232
18233    cx.update_editor(|editor, window, cx| {
18234        editor.change_selections(None, window, cx, |s| {
18235            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18236        });
18237    });
18238
18239    assert_indent_guides(
18240        0..3,
18241        vec![indent_guide(buffer_id, 1, 1, 0)],
18242        Some(vec![0]),
18243        &mut cx,
18244    );
18245}
18246
18247#[gpui::test]
18248async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18249    let (buffer_id, mut cx) = setup_indent_guides_editor(
18250        &"
18251    fn main() {
18252        if 1 == 2 {
18253            let a = 1;
18254        }
18255    }"
18256        .unindent(),
18257        cx,
18258    )
18259    .await;
18260
18261    cx.update_editor(|editor, window, cx| {
18262        editor.change_selections(None, window, cx, |s| {
18263            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18264        });
18265    });
18266
18267    assert_indent_guides(
18268        0..4,
18269        vec![
18270            indent_guide(buffer_id, 1, 3, 0),
18271            indent_guide(buffer_id, 2, 2, 1),
18272        ],
18273        Some(vec![1]),
18274        &mut cx,
18275    );
18276
18277    cx.update_editor(|editor, window, cx| {
18278        editor.change_selections(None, window, cx, |s| {
18279            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18280        });
18281    });
18282
18283    assert_indent_guides(
18284        0..4,
18285        vec![
18286            indent_guide(buffer_id, 1, 3, 0),
18287            indent_guide(buffer_id, 2, 2, 1),
18288        ],
18289        Some(vec![1]),
18290        &mut cx,
18291    );
18292
18293    cx.update_editor(|editor, window, cx| {
18294        editor.change_selections(None, window, cx, |s| {
18295            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18296        });
18297    });
18298
18299    assert_indent_guides(
18300        0..4,
18301        vec![
18302            indent_guide(buffer_id, 1, 3, 0),
18303            indent_guide(buffer_id, 2, 2, 1),
18304        ],
18305        Some(vec![0]),
18306        &mut cx,
18307    );
18308}
18309
18310#[gpui::test]
18311async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18312    let (buffer_id, mut cx) = setup_indent_guides_editor(
18313        &"
18314    fn main() {
18315        let a = 1;
18316
18317        let b = 2;
18318    }"
18319        .unindent(),
18320        cx,
18321    )
18322    .await;
18323
18324    cx.update_editor(|editor, window, cx| {
18325        editor.change_selections(None, window, cx, |s| {
18326            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18327        });
18328    });
18329
18330    assert_indent_guides(
18331        0..5,
18332        vec![indent_guide(buffer_id, 1, 3, 0)],
18333        Some(vec![0]),
18334        &mut cx,
18335    );
18336}
18337
18338#[gpui::test]
18339async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18340    let (buffer_id, mut cx) = setup_indent_guides_editor(
18341        &"
18342    def m:
18343        a = 1
18344        pass"
18345            .unindent(),
18346        cx,
18347    )
18348    .await;
18349
18350    cx.update_editor(|editor, window, cx| {
18351        editor.change_selections(None, window, cx, |s| {
18352            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18353        });
18354    });
18355
18356    assert_indent_guides(
18357        0..3,
18358        vec![indent_guide(buffer_id, 1, 2, 0)],
18359        Some(vec![0]),
18360        &mut cx,
18361    );
18362}
18363
18364#[gpui::test]
18365async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18366    init_test(cx, |_| {});
18367    let mut cx = EditorTestContext::new(cx).await;
18368    let text = indoc! {
18369        "
18370        impl A {
18371            fn b() {
18372                0;
18373                3;
18374                5;
18375                6;
18376                7;
18377            }
18378        }
18379        "
18380    };
18381    let base_text = indoc! {
18382        "
18383        impl A {
18384            fn b() {
18385                0;
18386                1;
18387                2;
18388                3;
18389                4;
18390            }
18391            fn c() {
18392                5;
18393                6;
18394                7;
18395            }
18396        }
18397        "
18398    };
18399
18400    cx.update_editor(|editor, window, cx| {
18401        editor.set_text(text, window, cx);
18402
18403        editor.buffer().update(cx, |multibuffer, cx| {
18404            let buffer = multibuffer.as_singleton().unwrap();
18405            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18406
18407            multibuffer.set_all_diff_hunks_expanded(cx);
18408            multibuffer.add_diff(diff, cx);
18409
18410            buffer.read(cx).remote_id()
18411        })
18412    });
18413    cx.run_until_parked();
18414
18415    cx.assert_state_with_diff(
18416        indoc! { "
18417          impl A {
18418              fn b() {
18419                  0;
18420        -         1;
18421        -         2;
18422                  3;
18423        -         4;
18424        -     }
18425        -     fn c() {
18426                  5;
18427                  6;
18428                  7;
18429              }
18430          }
18431          ˇ"
18432        }
18433        .to_string(),
18434    );
18435
18436    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18437        editor
18438            .snapshot(window, cx)
18439            .buffer_snapshot
18440            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18441            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18442            .collect::<Vec<_>>()
18443    });
18444    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18445    assert_eq!(
18446        actual_guides,
18447        vec![
18448            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18449            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18450            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18451        ]
18452    );
18453}
18454
18455#[gpui::test]
18456async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18457    init_test(cx, |_| {});
18458    let mut cx = EditorTestContext::new(cx).await;
18459
18460    let diff_base = r#"
18461        a
18462        b
18463        c
18464        "#
18465    .unindent();
18466
18467    cx.set_state(
18468        &r#"
18469        ˇA
18470        b
18471        C
18472        "#
18473        .unindent(),
18474    );
18475    cx.set_head_text(&diff_base);
18476    cx.update_editor(|editor, window, cx| {
18477        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18478    });
18479    executor.run_until_parked();
18480
18481    let both_hunks_expanded = r#"
18482        - a
18483        + ˇA
18484          b
18485        - c
18486        + C
18487        "#
18488    .unindent();
18489
18490    cx.assert_state_with_diff(both_hunks_expanded.clone());
18491
18492    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18493        let snapshot = editor.snapshot(window, cx);
18494        let hunks = editor
18495            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18496            .collect::<Vec<_>>();
18497        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18498        let buffer_id = hunks[0].buffer_id;
18499        hunks
18500            .into_iter()
18501            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18502            .collect::<Vec<_>>()
18503    });
18504    assert_eq!(hunk_ranges.len(), 2);
18505
18506    cx.update_editor(|editor, _, cx| {
18507        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18508    });
18509    executor.run_until_parked();
18510
18511    let second_hunk_expanded = r#"
18512          ˇA
18513          b
18514        - c
18515        + C
18516        "#
18517    .unindent();
18518
18519    cx.assert_state_with_diff(second_hunk_expanded);
18520
18521    cx.update_editor(|editor, _, cx| {
18522        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18523    });
18524    executor.run_until_parked();
18525
18526    cx.assert_state_with_diff(both_hunks_expanded.clone());
18527
18528    cx.update_editor(|editor, _, cx| {
18529        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18530    });
18531    executor.run_until_parked();
18532
18533    let first_hunk_expanded = r#"
18534        - a
18535        + ˇA
18536          b
18537          C
18538        "#
18539    .unindent();
18540
18541    cx.assert_state_with_diff(first_hunk_expanded);
18542
18543    cx.update_editor(|editor, _, cx| {
18544        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18545    });
18546    executor.run_until_parked();
18547
18548    cx.assert_state_with_diff(both_hunks_expanded);
18549
18550    cx.set_state(
18551        &r#"
18552        ˇA
18553        b
18554        "#
18555        .unindent(),
18556    );
18557    cx.run_until_parked();
18558
18559    // TODO this cursor position seems bad
18560    cx.assert_state_with_diff(
18561        r#"
18562        - ˇa
18563        + A
18564          b
18565        "#
18566        .unindent(),
18567    );
18568
18569    cx.update_editor(|editor, window, cx| {
18570        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18571    });
18572
18573    cx.assert_state_with_diff(
18574        r#"
18575            - ˇa
18576            + A
18577              b
18578            - c
18579            "#
18580        .unindent(),
18581    );
18582
18583    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18584        let snapshot = editor.snapshot(window, cx);
18585        let hunks = editor
18586            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18587            .collect::<Vec<_>>();
18588        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18589        let buffer_id = hunks[0].buffer_id;
18590        hunks
18591            .into_iter()
18592            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18593            .collect::<Vec<_>>()
18594    });
18595    assert_eq!(hunk_ranges.len(), 2);
18596
18597    cx.update_editor(|editor, _, cx| {
18598        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18599    });
18600    executor.run_until_parked();
18601
18602    cx.assert_state_with_diff(
18603        r#"
18604        - ˇa
18605        + A
18606          b
18607        "#
18608        .unindent(),
18609    );
18610}
18611
18612#[gpui::test]
18613async fn test_toggle_deletion_hunk_at_start_of_file(
18614    executor: BackgroundExecutor,
18615    cx: &mut TestAppContext,
18616) {
18617    init_test(cx, |_| {});
18618    let mut cx = EditorTestContext::new(cx).await;
18619
18620    let diff_base = r#"
18621        a
18622        b
18623        c
18624        "#
18625    .unindent();
18626
18627    cx.set_state(
18628        &r#"
18629        ˇb
18630        c
18631        "#
18632        .unindent(),
18633    );
18634    cx.set_head_text(&diff_base);
18635    cx.update_editor(|editor, window, cx| {
18636        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18637    });
18638    executor.run_until_parked();
18639
18640    let hunk_expanded = r#"
18641        - a
18642          ˇb
18643          c
18644        "#
18645    .unindent();
18646
18647    cx.assert_state_with_diff(hunk_expanded.clone());
18648
18649    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18650        let snapshot = editor.snapshot(window, cx);
18651        let hunks = editor
18652            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18653            .collect::<Vec<_>>();
18654        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18655        let buffer_id = hunks[0].buffer_id;
18656        hunks
18657            .into_iter()
18658            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18659            .collect::<Vec<_>>()
18660    });
18661    assert_eq!(hunk_ranges.len(), 1);
18662
18663    cx.update_editor(|editor, _, cx| {
18664        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18665    });
18666    executor.run_until_parked();
18667
18668    let hunk_collapsed = r#"
18669          ˇb
18670          c
18671        "#
18672    .unindent();
18673
18674    cx.assert_state_with_diff(hunk_collapsed);
18675
18676    cx.update_editor(|editor, _, cx| {
18677        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18678    });
18679    executor.run_until_parked();
18680
18681    cx.assert_state_with_diff(hunk_expanded.clone());
18682}
18683
18684#[gpui::test]
18685async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18686    init_test(cx, |_| {});
18687
18688    let fs = FakeFs::new(cx.executor());
18689    fs.insert_tree(
18690        path!("/test"),
18691        json!({
18692            ".git": {},
18693            "file-1": "ONE\n",
18694            "file-2": "TWO\n",
18695            "file-3": "THREE\n",
18696        }),
18697    )
18698    .await;
18699
18700    fs.set_head_for_repo(
18701        path!("/test/.git").as_ref(),
18702        &[
18703            ("file-1".into(), "one\n".into()),
18704            ("file-2".into(), "two\n".into()),
18705            ("file-3".into(), "three\n".into()),
18706        ],
18707        "deadbeef",
18708    );
18709
18710    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18711    let mut buffers = vec![];
18712    for i in 1..=3 {
18713        let buffer = project
18714            .update(cx, |project, cx| {
18715                let path = format!(path!("/test/file-{}"), i);
18716                project.open_local_buffer(path, cx)
18717            })
18718            .await
18719            .unwrap();
18720        buffers.push(buffer);
18721    }
18722
18723    let multibuffer = cx.new(|cx| {
18724        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18725        multibuffer.set_all_diff_hunks_expanded(cx);
18726        for buffer in &buffers {
18727            let snapshot = buffer.read(cx).snapshot();
18728            multibuffer.set_excerpts_for_path(
18729                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18730                buffer.clone(),
18731                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18732                DEFAULT_MULTIBUFFER_CONTEXT,
18733                cx,
18734            );
18735        }
18736        multibuffer
18737    });
18738
18739    let editor = cx.add_window(|window, cx| {
18740        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18741    });
18742    cx.run_until_parked();
18743
18744    let snapshot = editor
18745        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18746        .unwrap();
18747    let hunks = snapshot
18748        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18749        .map(|hunk| match hunk {
18750            DisplayDiffHunk::Unfolded {
18751                display_row_range, ..
18752            } => display_row_range,
18753            DisplayDiffHunk::Folded { .. } => unreachable!(),
18754        })
18755        .collect::<Vec<_>>();
18756    assert_eq!(
18757        hunks,
18758        [
18759            DisplayRow(2)..DisplayRow(4),
18760            DisplayRow(7)..DisplayRow(9),
18761            DisplayRow(12)..DisplayRow(14),
18762        ]
18763    );
18764}
18765
18766#[gpui::test]
18767async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18768    init_test(cx, |_| {});
18769
18770    let mut cx = EditorTestContext::new(cx).await;
18771    cx.set_head_text(indoc! { "
18772        one
18773        two
18774        three
18775        four
18776        five
18777        "
18778    });
18779    cx.set_index_text(indoc! { "
18780        one
18781        two
18782        three
18783        four
18784        five
18785        "
18786    });
18787    cx.set_state(indoc! {"
18788        one
18789        TWO
18790        ˇTHREE
18791        FOUR
18792        five
18793    "});
18794    cx.run_until_parked();
18795    cx.update_editor(|editor, window, cx| {
18796        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18797    });
18798    cx.run_until_parked();
18799    cx.assert_index_text(Some(indoc! {"
18800        one
18801        TWO
18802        THREE
18803        FOUR
18804        five
18805    "}));
18806    cx.set_state(indoc! { "
18807        one
18808        TWO
18809        ˇTHREE-HUNDRED
18810        FOUR
18811        five
18812    "});
18813    cx.run_until_parked();
18814    cx.update_editor(|editor, window, cx| {
18815        let snapshot = editor.snapshot(window, cx);
18816        let hunks = editor
18817            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18818            .collect::<Vec<_>>();
18819        assert_eq!(hunks.len(), 1);
18820        assert_eq!(
18821            hunks[0].status(),
18822            DiffHunkStatus {
18823                kind: DiffHunkStatusKind::Modified,
18824                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18825            }
18826        );
18827
18828        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18829    });
18830    cx.run_until_parked();
18831    cx.assert_index_text(Some(indoc! {"
18832        one
18833        TWO
18834        THREE-HUNDRED
18835        FOUR
18836        five
18837    "}));
18838}
18839
18840#[gpui::test]
18841fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18842    init_test(cx, |_| {});
18843
18844    let editor = cx.add_window(|window, cx| {
18845        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18846        build_editor(buffer, window, cx)
18847    });
18848
18849    let render_args = Arc::new(Mutex::new(None));
18850    let snapshot = editor
18851        .update(cx, |editor, window, cx| {
18852            let snapshot = editor.buffer().read(cx).snapshot(cx);
18853            let range =
18854                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18855
18856            struct RenderArgs {
18857                row: MultiBufferRow,
18858                folded: bool,
18859                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18860            }
18861
18862            let crease = Crease::inline(
18863                range,
18864                FoldPlaceholder::test(),
18865                {
18866                    let toggle_callback = render_args.clone();
18867                    move |row, folded, callback, _window, _cx| {
18868                        *toggle_callback.lock() = Some(RenderArgs {
18869                            row,
18870                            folded,
18871                            callback,
18872                        });
18873                        div()
18874                    }
18875                },
18876                |_row, _folded, _window, _cx| div(),
18877            );
18878
18879            editor.insert_creases(Some(crease), cx);
18880            let snapshot = editor.snapshot(window, cx);
18881            let _div = snapshot.render_crease_toggle(
18882                MultiBufferRow(1),
18883                false,
18884                cx.entity().clone(),
18885                window,
18886                cx,
18887            );
18888            snapshot
18889        })
18890        .unwrap();
18891
18892    let render_args = render_args.lock().take().unwrap();
18893    assert_eq!(render_args.row, MultiBufferRow(1));
18894    assert!(!render_args.folded);
18895    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18896
18897    cx.update_window(*editor, |_, window, cx| {
18898        (render_args.callback)(true, window, cx)
18899    })
18900    .unwrap();
18901    let snapshot = editor
18902        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18903        .unwrap();
18904    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18905
18906    cx.update_window(*editor, |_, window, cx| {
18907        (render_args.callback)(false, window, cx)
18908    })
18909    .unwrap();
18910    let snapshot = editor
18911        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18912        .unwrap();
18913    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18914}
18915
18916#[gpui::test]
18917async fn test_input_text(cx: &mut TestAppContext) {
18918    init_test(cx, |_| {});
18919    let mut cx = EditorTestContext::new(cx).await;
18920
18921    cx.set_state(
18922        &r#"ˇone
18923        two
18924
18925        three
18926        fourˇ
18927        five
18928
18929        siˇx"#
18930            .unindent(),
18931    );
18932
18933    cx.dispatch_action(HandleInput(String::new()));
18934    cx.assert_editor_state(
18935        &r#"ˇone
18936        two
18937
18938        three
18939        fourˇ
18940        five
18941
18942        siˇx"#
18943            .unindent(),
18944    );
18945
18946    cx.dispatch_action(HandleInput("AAAA".to_string()));
18947    cx.assert_editor_state(
18948        &r#"AAAAˇone
18949        two
18950
18951        three
18952        fourAAAAˇ
18953        five
18954
18955        siAAAAˇx"#
18956            .unindent(),
18957    );
18958}
18959
18960#[gpui::test]
18961async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18962    init_test(cx, |_| {});
18963
18964    let mut cx = EditorTestContext::new(cx).await;
18965    cx.set_state(
18966        r#"let foo = 1;
18967let foo = 2;
18968let foo = 3;
18969let fooˇ = 4;
18970let foo = 5;
18971let foo = 6;
18972let foo = 7;
18973let foo = 8;
18974let foo = 9;
18975let foo = 10;
18976let foo = 11;
18977let foo = 12;
18978let foo = 13;
18979let foo = 14;
18980let foo = 15;"#,
18981    );
18982
18983    cx.update_editor(|e, window, cx| {
18984        assert_eq!(
18985            e.next_scroll_position,
18986            NextScrollCursorCenterTopBottom::Center,
18987            "Default next scroll direction is center",
18988        );
18989
18990        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18991        assert_eq!(
18992            e.next_scroll_position,
18993            NextScrollCursorCenterTopBottom::Top,
18994            "After center, next scroll direction should be top",
18995        );
18996
18997        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18998        assert_eq!(
18999            e.next_scroll_position,
19000            NextScrollCursorCenterTopBottom::Bottom,
19001            "After top, next scroll direction should be bottom",
19002        );
19003
19004        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19005        assert_eq!(
19006            e.next_scroll_position,
19007            NextScrollCursorCenterTopBottom::Center,
19008            "After bottom, scrolling should start over",
19009        );
19010
19011        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19012        assert_eq!(
19013            e.next_scroll_position,
19014            NextScrollCursorCenterTopBottom::Top,
19015            "Scrolling continues if retriggered fast enough"
19016        );
19017    });
19018
19019    cx.executor()
19020        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19021    cx.executor().run_until_parked();
19022    cx.update_editor(|e, _, _| {
19023        assert_eq!(
19024            e.next_scroll_position,
19025            NextScrollCursorCenterTopBottom::Center,
19026            "If scrolling is not triggered fast enough, it should reset"
19027        );
19028    });
19029}
19030
19031#[gpui::test]
19032async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19033    init_test(cx, |_| {});
19034    let mut cx = EditorLspTestContext::new_rust(
19035        lsp::ServerCapabilities {
19036            definition_provider: Some(lsp::OneOf::Left(true)),
19037            references_provider: Some(lsp::OneOf::Left(true)),
19038            ..lsp::ServerCapabilities::default()
19039        },
19040        cx,
19041    )
19042    .await;
19043
19044    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19045        let go_to_definition = cx
19046            .lsp
19047            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19048                move |params, _| async move {
19049                    if empty_go_to_definition {
19050                        Ok(None)
19051                    } else {
19052                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19053                            uri: params.text_document_position_params.text_document.uri,
19054                            range: lsp::Range::new(
19055                                lsp::Position::new(4, 3),
19056                                lsp::Position::new(4, 6),
19057                            ),
19058                        })))
19059                    }
19060                },
19061            );
19062        let references = cx
19063            .lsp
19064            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19065                Ok(Some(vec![lsp::Location {
19066                    uri: params.text_document_position.text_document.uri,
19067                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19068                }]))
19069            });
19070        (go_to_definition, references)
19071    };
19072
19073    cx.set_state(
19074        &r#"fn one() {
19075            let mut a = ˇtwo();
19076        }
19077
19078        fn two() {}"#
19079            .unindent(),
19080    );
19081    set_up_lsp_handlers(false, &mut cx);
19082    let navigated = cx
19083        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19084        .await
19085        .expect("Failed to navigate to definition");
19086    assert_eq!(
19087        navigated,
19088        Navigated::Yes,
19089        "Should have navigated to definition from the GetDefinition response"
19090    );
19091    cx.assert_editor_state(
19092        &r#"fn one() {
19093            let mut a = two();
19094        }
19095
19096        fn «twoˇ»() {}"#
19097            .unindent(),
19098    );
19099
19100    let editors = cx.update_workspace(|workspace, _, cx| {
19101        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19102    });
19103    cx.update_editor(|_, _, test_editor_cx| {
19104        assert_eq!(
19105            editors.len(),
19106            1,
19107            "Initially, only one, test, editor should be open in the workspace"
19108        );
19109        assert_eq!(
19110            test_editor_cx.entity(),
19111            editors.last().expect("Asserted len is 1").clone()
19112        );
19113    });
19114
19115    set_up_lsp_handlers(true, &mut cx);
19116    let navigated = cx
19117        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19118        .await
19119        .expect("Failed to navigate to lookup references");
19120    assert_eq!(
19121        navigated,
19122        Navigated::Yes,
19123        "Should have navigated to references as a fallback after empty GoToDefinition response"
19124    );
19125    // We should not change the selections in the existing file,
19126    // if opening another milti buffer with the references
19127    cx.assert_editor_state(
19128        &r#"fn one() {
19129            let mut a = two();
19130        }
19131
19132        fn «twoˇ»() {}"#
19133            .unindent(),
19134    );
19135    let editors = cx.update_workspace(|workspace, _, cx| {
19136        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19137    });
19138    cx.update_editor(|_, _, test_editor_cx| {
19139        assert_eq!(
19140            editors.len(),
19141            2,
19142            "After falling back to references search, we open a new editor with the results"
19143        );
19144        let references_fallback_text = editors
19145            .into_iter()
19146            .find(|new_editor| *new_editor != test_editor_cx.entity())
19147            .expect("Should have one non-test editor now")
19148            .read(test_editor_cx)
19149            .text(test_editor_cx);
19150        assert_eq!(
19151            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19152            "Should use the range from the references response and not the GoToDefinition one"
19153        );
19154    });
19155}
19156
19157#[gpui::test]
19158async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19159    init_test(cx, |_| {});
19160    cx.update(|cx| {
19161        let mut editor_settings = EditorSettings::get_global(cx).clone();
19162        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19163        EditorSettings::override_global(editor_settings, cx);
19164    });
19165    let mut cx = EditorLspTestContext::new_rust(
19166        lsp::ServerCapabilities {
19167            definition_provider: Some(lsp::OneOf::Left(true)),
19168            references_provider: Some(lsp::OneOf::Left(true)),
19169            ..lsp::ServerCapabilities::default()
19170        },
19171        cx,
19172    )
19173    .await;
19174    let original_state = r#"fn one() {
19175        let mut a = ˇtwo();
19176    }
19177
19178    fn two() {}"#
19179        .unindent();
19180    cx.set_state(&original_state);
19181
19182    let mut go_to_definition = cx
19183        .lsp
19184        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19185            move |_, _| async move { Ok(None) },
19186        );
19187    let _references = cx
19188        .lsp
19189        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19190            panic!("Should not call for references with no go to definition fallback")
19191        });
19192
19193    let navigated = cx
19194        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19195        .await
19196        .expect("Failed to navigate to lookup references");
19197    go_to_definition
19198        .next()
19199        .await
19200        .expect("Should have called the go_to_definition handler");
19201
19202    assert_eq!(
19203        navigated,
19204        Navigated::No,
19205        "Should have navigated to references as a fallback after empty GoToDefinition response"
19206    );
19207    cx.assert_editor_state(&original_state);
19208    let editors = cx.update_workspace(|workspace, _, cx| {
19209        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19210    });
19211    cx.update_editor(|_, _, _| {
19212        assert_eq!(
19213            editors.len(),
19214            1,
19215            "After unsuccessful fallback, no other editor should have been opened"
19216        );
19217    });
19218}
19219
19220#[gpui::test]
19221async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19222    init_test(cx, |_| {});
19223
19224    let language = Arc::new(Language::new(
19225        LanguageConfig::default(),
19226        Some(tree_sitter_rust::LANGUAGE.into()),
19227    ));
19228
19229    let text = r#"
19230        #[cfg(test)]
19231        mod tests() {
19232            #[test]
19233            fn runnable_1() {
19234                let a = 1;
19235            }
19236
19237            #[test]
19238            fn runnable_2() {
19239                let a = 1;
19240                let b = 2;
19241            }
19242        }
19243    "#
19244    .unindent();
19245
19246    let fs = FakeFs::new(cx.executor());
19247    fs.insert_file("/file.rs", Default::default()).await;
19248
19249    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19250    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19251    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19252    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19253    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19254
19255    let editor = cx.new_window_entity(|window, cx| {
19256        Editor::new(
19257            EditorMode::full(),
19258            multi_buffer,
19259            Some(project.clone()),
19260            window,
19261            cx,
19262        )
19263    });
19264
19265    editor.update_in(cx, |editor, window, cx| {
19266        let snapshot = editor.buffer().read(cx).snapshot(cx);
19267        editor.tasks.insert(
19268            (buffer.read(cx).remote_id(), 3),
19269            RunnableTasks {
19270                templates: vec![],
19271                offset: snapshot.anchor_before(43),
19272                column: 0,
19273                extra_variables: HashMap::default(),
19274                context_range: BufferOffset(43)..BufferOffset(85),
19275            },
19276        );
19277        editor.tasks.insert(
19278            (buffer.read(cx).remote_id(), 8),
19279            RunnableTasks {
19280                templates: vec![],
19281                offset: snapshot.anchor_before(86),
19282                column: 0,
19283                extra_variables: HashMap::default(),
19284                context_range: BufferOffset(86)..BufferOffset(191),
19285            },
19286        );
19287
19288        // Test finding task when cursor is inside function body
19289        editor.change_selections(None, window, cx, |s| {
19290            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19291        });
19292        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19293        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19294
19295        // Test finding task when cursor is on function name
19296        editor.change_selections(None, window, cx, |s| {
19297            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19298        });
19299        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19300        assert_eq!(row, 8, "Should find task when cursor is on function name");
19301    });
19302}
19303
19304#[gpui::test]
19305async fn test_folding_buffers(cx: &mut TestAppContext) {
19306    init_test(cx, |_| {});
19307
19308    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19309    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19310    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19311
19312    let fs = FakeFs::new(cx.executor());
19313    fs.insert_tree(
19314        path!("/a"),
19315        json!({
19316            "first.rs": sample_text_1,
19317            "second.rs": sample_text_2,
19318            "third.rs": sample_text_3,
19319        }),
19320    )
19321    .await;
19322    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19323    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19324    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19325    let worktree = project.update(cx, |project, cx| {
19326        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19327        assert_eq!(worktrees.len(), 1);
19328        worktrees.pop().unwrap()
19329    });
19330    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19331
19332    let buffer_1 = project
19333        .update(cx, |project, cx| {
19334            project.open_buffer((worktree_id, "first.rs"), cx)
19335        })
19336        .await
19337        .unwrap();
19338    let buffer_2 = project
19339        .update(cx, |project, cx| {
19340            project.open_buffer((worktree_id, "second.rs"), cx)
19341        })
19342        .await
19343        .unwrap();
19344    let buffer_3 = project
19345        .update(cx, |project, cx| {
19346            project.open_buffer((worktree_id, "third.rs"), cx)
19347        })
19348        .await
19349        .unwrap();
19350
19351    let multi_buffer = cx.new(|cx| {
19352        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19353        multi_buffer.push_excerpts(
19354            buffer_1.clone(),
19355            [
19356                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19357                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19358                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19359            ],
19360            cx,
19361        );
19362        multi_buffer.push_excerpts(
19363            buffer_2.clone(),
19364            [
19365                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19366                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19367                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19368            ],
19369            cx,
19370        );
19371        multi_buffer.push_excerpts(
19372            buffer_3.clone(),
19373            [
19374                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19375                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19376                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19377            ],
19378            cx,
19379        );
19380        multi_buffer
19381    });
19382    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19383        Editor::new(
19384            EditorMode::full(),
19385            multi_buffer.clone(),
19386            Some(project.clone()),
19387            window,
19388            cx,
19389        )
19390    });
19391
19392    assert_eq!(
19393        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19394        "\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",
19395    );
19396
19397    multi_buffer_editor.update(cx, |editor, cx| {
19398        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19399    });
19400    assert_eq!(
19401        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19402        "\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",
19403        "After folding the first buffer, its text should not be displayed"
19404    );
19405
19406    multi_buffer_editor.update(cx, |editor, cx| {
19407        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19408    });
19409    assert_eq!(
19410        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19411        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19412        "After folding the second buffer, its text should not be displayed"
19413    );
19414
19415    multi_buffer_editor.update(cx, |editor, cx| {
19416        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19417    });
19418    assert_eq!(
19419        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19420        "\n\n\n\n\n",
19421        "After folding the third buffer, its text should not be displayed"
19422    );
19423
19424    // Emulate selection inside the fold logic, that should work
19425    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19426        editor
19427            .snapshot(window, cx)
19428            .next_line_boundary(Point::new(0, 4));
19429    });
19430
19431    multi_buffer_editor.update(cx, |editor, cx| {
19432        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19433    });
19434    assert_eq!(
19435        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19436        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19437        "After unfolding the second buffer, its text should be displayed"
19438    );
19439
19440    // Typing inside of buffer 1 causes that buffer to be unfolded.
19441    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19442        assert_eq!(
19443            multi_buffer
19444                .read(cx)
19445                .snapshot(cx)
19446                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19447                .collect::<String>(),
19448            "bbbb"
19449        );
19450        editor.change_selections(None, window, cx, |selections| {
19451            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19452        });
19453        editor.handle_input("B", window, cx);
19454    });
19455
19456    assert_eq!(
19457        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19458        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19459        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19460    );
19461
19462    multi_buffer_editor.update(cx, |editor, cx| {
19463        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19464    });
19465    assert_eq!(
19466        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19467        "\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",
19468        "After unfolding the all buffers, all original text should be displayed"
19469    );
19470}
19471
19472#[gpui::test]
19473async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19474    init_test(cx, |_| {});
19475
19476    let sample_text_1 = "1111\n2222\n3333".to_string();
19477    let sample_text_2 = "4444\n5555\n6666".to_string();
19478    let sample_text_3 = "7777\n8888\n9999".to_string();
19479
19480    let fs = FakeFs::new(cx.executor());
19481    fs.insert_tree(
19482        path!("/a"),
19483        json!({
19484            "first.rs": sample_text_1,
19485            "second.rs": sample_text_2,
19486            "third.rs": sample_text_3,
19487        }),
19488    )
19489    .await;
19490    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19491    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19492    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19493    let worktree = project.update(cx, |project, cx| {
19494        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19495        assert_eq!(worktrees.len(), 1);
19496        worktrees.pop().unwrap()
19497    });
19498    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19499
19500    let buffer_1 = project
19501        .update(cx, |project, cx| {
19502            project.open_buffer((worktree_id, "first.rs"), cx)
19503        })
19504        .await
19505        .unwrap();
19506    let buffer_2 = project
19507        .update(cx, |project, cx| {
19508            project.open_buffer((worktree_id, "second.rs"), cx)
19509        })
19510        .await
19511        .unwrap();
19512    let buffer_3 = project
19513        .update(cx, |project, cx| {
19514            project.open_buffer((worktree_id, "third.rs"), cx)
19515        })
19516        .await
19517        .unwrap();
19518
19519    let multi_buffer = cx.new(|cx| {
19520        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19521        multi_buffer.push_excerpts(
19522            buffer_1.clone(),
19523            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19524            cx,
19525        );
19526        multi_buffer.push_excerpts(
19527            buffer_2.clone(),
19528            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19529            cx,
19530        );
19531        multi_buffer.push_excerpts(
19532            buffer_3.clone(),
19533            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19534            cx,
19535        );
19536        multi_buffer
19537    });
19538
19539    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19540        Editor::new(
19541            EditorMode::full(),
19542            multi_buffer,
19543            Some(project.clone()),
19544            window,
19545            cx,
19546        )
19547    });
19548
19549    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19550    assert_eq!(
19551        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19552        full_text,
19553    );
19554
19555    multi_buffer_editor.update(cx, |editor, cx| {
19556        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19557    });
19558    assert_eq!(
19559        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19560        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19561        "After folding the first buffer, its text should not be displayed"
19562    );
19563
19564    multi_buffer_editor.update(cx, |editor, cx| {
19565        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19566    });
19567
19568    assert_eq!(
19569        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19570        "\n\n\n\n\n\n7777\n8888\n9999",
19571        "After folding the second buffer, its text should not be displayed"
19572    );
19573
19574    multi_buffer_editor.update(cx, |editor, cx| {
19575        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19576    });
19577    assert_eq!(
19578        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19579        "\n\n\n\n\n",
19580        "After folding the third buffer, its text should not be displayed"
19581    );
19582
19583    multi_buffer_editor.update(cx, |editor, cx| {
19584        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19585    });
19586    assert_eq!(
19587        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19588        "\n\n\n\n4444\n5555\n6666\n\n",
19589        "After unfolding the second buffer, its text should be displayed"
19590    );
19591
19592    multi_buffer_editor.update(cx, |editor, cx| {
19593        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19594    });
19595    assert_eq!(
19596        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19597        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19598        "After unfolding the first buffer, its text should be displayed"
19599    );
19600
19601    multi_buffer_editor.update(cx, |editor, cx| {
19602        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19603    });
19604    assert_eq!(
19605        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19606        full_text,
19607        "After unfolding all buffers, all original text should be displayed"
19608    );
19609}
19610
19611#[gpui::test]
19612async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19613    init_test(cx, |_| {});
19614
19615    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19616
19617    let fs = FakeFs::new(cx.executor());
19618    fs.insert_tree(
19619        path!("/a"),
19620        json!({
19621            "main.rs": sample_text,
19622        }),
19623    )
19624    .await;
19625    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19626    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19627    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19628    let worktree = project.update(cx, |project, cx| {
19629        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19630        assert_eq!(worktrees.len(), 1);
19631        worktrees.pop().unwrap()
19632    });
19633    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19634
19635    let buffer_1 = project
19636        .update(cx, |project, cx| {
19637            project.open_buffer((worktree_id, "main.rs"), cx)
19638        })
19639        .await
19640        .unwrap();
19641
19642    let multi_buffer = cx.new(|cx| {
19643        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19644        multi_buffer.push_excerpts(
19645            buffer_1.clone(),
19646            [ExcerptRange::new(
19647                Point::new(0, 0)
19648                    ..Point::new(
19649                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19650                        0,
19651                    ),
19652            )],
19653            cx,
19654        );
19655        multi_buffer
19656    });
19657    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19658        Editor::new(
19659            EditorMode::full(),
19660            multi_buffer,
19661            Some(project.clone()),
19662            window,
19663            cx,
19664        )
19665    });
19666
19667    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19668    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19669        enum TestHighlight {}
19670        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19671        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19672        editor.highlight_text::<TestHighlight>(
19673            vec![highlight_range.clone()],
19674            HighlightStyle::color(Hsla::green()),
19675            cx,
19676        );
19677        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
19678    });
19679
19680    let full_text = format!("\n\n{sample_text}");
19681    assert_eq!(
19682        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19683        full_text,
19684    );
19685}
19686
19687#[gpui::test]
19688async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19689    init_test(cx, |_| {});
19690    cx.update(|cx| {
19691        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19692            "keymaps/default-linux.json",
19693            cx,
19694        )
19695        .unwrap();
19696        cx.bind_keys(default_key_bindings);
19697    });
19698
19699    let (editor, cx) = cx.add_window_view(|window, cx| {
19700        let multi_buffer = MultiBuffer::build_multi(
19701            [
19702                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19703                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19704                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19705                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19706            ],
19707            cx,
19708        );
19709        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19710
19711        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19712        // fold all but the second buffer, so that we test navigating between two
19713        // adjacent folded buffers, as well as folded buffers at the start and
19714        // end the multibuffer
19715        editor.fold_buffer(buffer_ids[0], cx);
19716        editor.fold_buffer(buffer_ids[2], cx);
19717        editor.fold_buffer(buffer_ids[3], cx);
19718
19719        editor
19720    });
19721    cx.simulate_resize(size(px(1000.), px(1000.)));
19722
19723    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19724    cx.assert_excerpts_with_selections(indoc! {"
19725        [EXCERPT]
19726        ˇ[FOLDED]
19727        [EXCERPT]
19728        a1
19729        b1
19730        [EXCERPT]
19731        [FOLDED]
19732        [EXCERPT]
19733        [FOLDED]
19734        "
19735    });
19736    cx.simulate_keystroke("down");
19737    cx.assert_excerpts_with_selections(indoc! {"
19738        [EXCERPT]
19739        [FOLDED]
19740        [EXCERPT]
19741        ˇa1
19742        b1
19743        [EXCERPT]
19744        [FOLDED]
19745        [EXCERPT]
19746        [FOLDED]
19747        "
19748    });
19749    cx.simulate_keystroke("down");
19750    cx.assert_excerpts_with_selections(indoc! {"
19751        [EXCERPT]
19752        [FOLDED]
19753        [EXCERPT]
19754        a1
19755        ˇb1
19756        [EXCERPT]
19757        [FOLDED]
19758        [EXCERPT]
19759        [FOLDED]
19760        "
19761    });
19762    cx.simulate_keystroke("down");
19763    cx.assert_excerpts_with_selections(indoc! {"
19764        [EXCERPT]
19765        [FOLDED]
19766        [EXCERPT]
19767        a1
19768        b1
19769        ˇ[EXCERPT]
19770        [FOLDED]
19771        [EXCERPT]
19772        [FOLDED]
19773        "
19774    });
19775    cx.simulate_keystroke("down");
19776    cx.assert_excerpts_with_selections(indoc! {"
19777        [EXCERPT]
19778        [FOLDED]
19779        [EXCERPT]
19780        a1
19781        b1
19782        [EXCERPT]
19783        ˇ[FOLDED]
19784        [EXCERPT]
19785        [FOLDED]
19786        "
19787    });
19788    for _ in 0..5 {
19789        cx.simulate_keystroke("down");
19790        cx.assert_excerpts_with_selections(indoc! {"
19791            [EXCERPT]
19792            [FOLDED]
19793            [EXCERPT]
19794            a1
19795            b1
19796            [EXCERPT]
19797            [FOLDED]
19798            [EXCERPT]
19799            ˇ[FOLDED]
19800            "
19801        });
19802    }
19803
19804    cx.simulate_keystroke("up");
19805    cx.assert_excerpts_with_selections(indoc! {"
19806        [EXCERPT]
19807        [FOLDED]
19808        [EXCERPT]
19809        a1
19810        b1
19811        [EXCERPT]
19812        ˇ[FOLDED]
19813        [EXCERPT]
19814        [FOLDED]
19815        "
19816    });
19817    cx.simulate_keystroke("up");
19818    cx.assert_excerpts_with_selections(indoc! {"
19819        [EXCERPT]
19820        [FOLDED]
19821        [EXCERPT]
19822        a1
19823        b1
19824        ˇ[EXCERPT]
19825        [FOLDED]
19826        [EXCERPT]
19827        [FOLDED]
19828        "
19829    });
19830    cx.simulate_keystroke("up");
19831    cx.assert_excerpts_with_selections(indoc! {"
19832        [EXCERPT]
19833        [FOLDED]
19834        [EXCERPT]
19835        a1
19836        ˇb1
19837        [EXCERPT]
19838        [FOLDED]
19839        [EXCERPT]
19840        [FOLDED]
19841        "
19842    });
19843    cx.simulate_keystroke("up");
19844    cx.assert_excerpts_with_selections(indoc! {"
19845        [EXCERPT]
19846        [FOLDED]
19847        [EXCERPT]
19848        ˇa1
19849        b1
19850        [EXCERPT]
19851        [FOLDED]
19852        [EXCERPT]
19853        [FOLDED]
19854        "
19855    });
19856    for _ in 0..5 {
19857        cx.simulate_keystroke("up");
19858        cx.assert_excerpts_with_selections(indoc! {"
19859            [EXCERPT]
19860            ˇ[FOLDED]
19861            [EXCERPT]
19862            a1
19863            b1
19864            [EXCERPT]
19865            [FOLDED]
19866            [EXCERPT]
19867            [FOLDED]
19868            "
19869        });
19870    }
19871}
19872
19873#[gpui::test]
19874async fn test_inline_completion_text(cx: &mut TestAppContext) {
19875    init_test(cx, |_| {});
19876
19877    // Simple insertion
19878    assert_highlighted_edits(
19879        "Hello, world!",
19880        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19881        true,
19882        cx,
19883        |highlighted_edits, cx| {
19884            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19885            assert_eq!(highlighted_edits.highlights.len(), 1);
19886            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19887            assert_eq!(
19888                highlighted_edits.highlights[0].1.background_color,
19889                Some(cx.theme().status().created_background)
19890            );
19891        },
19892    )
19893    .await;
19894
19895    // Replacement
19896    assert_highlighted_edits(
19897        "This is a test.",
19898        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19899        false,
19900        cx,
19901        |highlighted_edits, cx| {
19902            assert_eq!(highlighted_edits.text, "That is a test.");
19903            assert_eq!(highlighted_edits.highlights.len(), 1);
19904            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19905            assert_eq!(
19906                highlighted_edits.highlights[0].1.background_color,
19907                Some(cx.theme().status().created_background)
19908            );
19909        },
19910    )
19911    .await;
19912
19913    // Multiple edits
19914    assert_highlighted_edits(
19915        "Hello, world!",
19916        vec![
19917            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19918            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19919        ],
19920        false,
19921        cx,
19922        |highlighted_edits, cx| {
19923            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19924            assert_eq!(highlighted_edits.highlights.len(), 2);
19925            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19926            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19927            assert_eq!(
19928                highlighted_edits.highlights[0].1.background_color,
19929                Some(cx.theme().status().created_background)
19930            );
19931            assert_eq!(
19932                highlighted_edits.highlights[1].1.background_color,
19933                Some(cx.theme().status().created_background)
19934            );
19935        },
19936    )
19937    .await;
19938
19939    // Multiple lines with edits
19940    assert_highlighted_edits(
19941        "First line\nSecond line\nThird line\nFourth line",
19942        vec![
19943            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19944            (
19945                Point::new(2, 0)..Point::new(2, 10),
19946                "New third line".to_string(),
19947            ),
19948            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19949        ],
19950        false,
19951        cx,
19952        |highlighted_edits, cx| {
19953            assert_eq!(
19954                highlighted_edits.text,
19955                "Second modified\nNew third line\nFourth updated line"
19956            );
19957            assert_eq!(highlighted_edits.highlights.len(), 3);
19958            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19959            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19960            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19961            for highlight in &highlighted_edits.highlights {
19962                assert_eq!(
19963                    highlight.1.background_color,
19964                    Some(cx.theme().status().created_background)
19965                );
19966            }
19967        },
19968    )
19969    .await;
19970}
19971
19972#[gpui::test]
19973async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19974    init_test(cx, |_| {});
19975
19976    // Deletion
19977    assert_highlighted_edits(
19978        "Hello, world!",
19979        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19980        true,
19981        cx,
19982        |highlighted_edits, cx| {
19983            assert_eq!(highlighted_edits.text, "Hello, world!");
19984            assert_eq!(highlighted_edits.highlights.len(), 1);
19985            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19986            assert_eq!(
19987                highlighted_edits.highlights[0].1.background_color,
19988                Some(cx.theme().status().deleted_background)
19989            );
19990        },
19991    )
19992    .await;
19993
19994    // Insertion
19995    assert_highlighted_edits(
19996        "Hello, world!",
19997        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19998        true,
19999        cx,
20000        |highlighted_edits, cx| {
20001            assert_eq!(highlighted_edits.highlights.len(), 1);
20002            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20003            assert_eq!(
20004                highlighted_edits.highlights[0].1.background_color,
20005                Some(cx.theme().status().created_background)
20006            );
20007        },
20008    )
20009    .await;
20010}
20011
20012async fn assert_highlighted_edits(
20013    text: &str,
20014    edits: Vec<(Range<Point>, String)>,
20015    include_deletions: bool,
20016    cx: &mut TestAppContext,
20017    assertion_fn: impl Fn(HighlightedText, &App),
20018) {
20019    let window = cx.add_window(|window, cx| {
20020        let buffer = MultiBuffer::build_simple(text, cx);
20021        Editor::new(EditorMode::full(), buffer, None, window, cx)
20022    });
20023    let cx = &mut VisualTestContext::from_window(*window, cx);
20024
20025    let (buffer, snapshot) = window
20026        .update(cx, |editor, _window, cx| {
20027            (
20028                editor.buffer().clone(),
20029                editor.buffer().read(cx).snapshot(cx),
20030            )
20031        })
20032        .unwrap();
20033
20034    let edits = edits
20035        .into_iter()
20036        .map(|(range, edit)| {
20037            (
20038                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20039                edit,
20040            )
20041        })
20042        .collect::<Vec<_>>();
20043
20044    let text_anchor_edits = edits
20045        .clone()
20046        .into_iter()
20047        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20048        .collect::<Vec<_>>();
20049
20050    let edit_preview = window
20051        .update(cx, |_, _window, cx| {
20052            buffer
20053                .read(cx)
20054                .as_singleton()
20055                .unwrap()
20056                .read(cx)
20057                .preview_edits(text_anchor_edits.into(), cx)
20058        })
20059        .unwrap()
20060        .await;
20061
20062    cx.update(|_window, cx| {
20063        let highlighted_edits = inline_completion_edit_text(
20064            &snapshot.as_singleton().unwrap().2,
20065            &edits,
20066            &edit_preview,
20067            include_deletions,
20068            cx,
20069        );
20070        assertion_fn(highlighted_edits, cx)
20071    });
20072}
20073
20074#[track_caller]
20075fn assert_breakpoint(
20076    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20077    path: &Arc<Path>,
20078    expected: Vec<(u32, Breakpoint)>,
20079) {
20080    if expected.len() == 0usize {
20081        assert!(!breakpoints.contains_key(path), "{}", path.display());
20082    } else {
20083        let mut breakpoint = breakpoints
20084            .get(path)
20085            .unwrap()
20086            .into_iter()
20087            .map(|breakpoint| {
20088                (
20089                    breakpoint.row,
20090                    Breakpoint {
20091                        message: breakpoint.message.clone(),
20092                        state: breakpoint.state,
20093                        condition: breakpoint.condition.clone(),
20094                        hit_condition: breakpoint.hit_condition.clone(),
20095                    },
20096                )
20097            })
20098            .collect::<Vec<_>>();
20099
20100        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20101
20102        assert_eq!(expected, breakpoint);
20103    }
20104}
20105
20106fn add_log_breakpoint_at_cursor(
20107    editor: &mut Editor,
20108    log_message: &str,
20109    window: &mut Window,
20110    cx: &mut Context<Editor>,
20111) {
20112    let (anchor, bp) = editor
20113        .breakpoints_at_cursors(window, cx)
20114        .first()
20115        .and_then(|(anchor, bp)| {
20116            if let Some(bp) = bp {
20117                Some((*anchor, bp.clone()))
20118            } else {
20119                None
20120            }
20121        })
20122        .unwrap_or_else(|| {
20123            let cursor_position: Point = editor.selections.newest(cx).head();
20124
20125            let breakpoint_position = editor
20126                .snapshot(window, cx)
20127                .display_snapshot
20128                .buffer_snapshot
20129                .anchor_before(Point::new(cursor_position.row, 0));
20130
20131            (breakpoint_position, Breakpoint::new_log(&log_message))
20132        });
20133
20134    editor.edit_breakpoint_at_anchor(
20135        anchor,
20136        bp,
20137        BreakpointEditAction::EditLogMessage(log_message.into()),
20138        cx,
20139    );
20140}
20141
20142#[gpui::test]
20143async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20144    init_test(cx, |_| {});
20145
20146    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20147    let fs = FakeFs::new(cx.executor());
20148    fs.insert_tree(
20149        path!("/a"),
20150        json!({
20151            "main.rs": sample_text,
20152        }),
20153    )
20154    .await;
20155    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20156    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20157    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20158
20159    let fs = FakeFs::new(cx.executor());
20160    fs.insert_tree(
20161        path!("/a"),
20162        json!({
20163            "main.rs": sample_text,
20164        }),
20165    )
20166    .await;
20167    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20168    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20169    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20170    let worktree_id = workspace
20171        .update(cx, |workspace, _window, cx| {
20172            workspace.project().update(cx, |project, cx| {
20173                project.worktrees(cx).next().unwrap().read(cx).id()
20174            })
20175        })
20176        .unwrap();
20177
20178    let buffer = project
20179        .update(cx, |project, cx| {
20180            project.open_buffer((worktree_id, "main.rs"), cx)
20181        })
20182        .await
20183        .unwrap();
20184
20185    let (editor, cx) = cx.add_window_view(|window, cx| {
20186        Editor::new(
20187            EditorMode::full(),
20188            MultiBuffer::build_from_buffer(buffer, cx),
20189            Some(project.clone()),
20190            window,
20191            cx,
20192        )
20193    });
20194
20195    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20196    let abs_path = project.read_with(cx, |project, cx| {
20197        project
20198            .absolute_path(&project_path, cx)
20199            .map(|path_buf| Arc::from(path_buf.to_owned()))
20200            .unwrap()
20201    });
20202
20203    // assert we can add breakpoint on the first line
20204    editor.update_in(cx, |editor, window, cx| {
20205        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20206        editor.move_to_end(&MoveToEnd, window, cx);
20207        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20208    });
20209
20210    let breakpoints = editor.update(cx, |editor, cx| {
20211        editor
20212            .breakpoint_store()
20213            .as_ref()
20214            .unwrap()
20215            .read(cx)
20216            .all_source_breakpoints(cx)
20217            .clone()
20218    });
20219
20220    assert_eq!(1, breakpoints.len());
20221    assert_breakpoint(
20222        &breakpoints,
20223        &abs_path,
20224        vec![
20225            (0, Breakpoint::new_standard()),
20226            (3, Breakpoint::new_standard()),
20227        ],
20228    );
20229
20230    editor.update_in(cx, |editor, window, cx| {
20231        editor.move_to_beginning(&MoveToBeginning, window, cx);
20232        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20233    });
20234
20235    let breakpoints = editor.update(cx, |editor, cx| {
20236        editor
20237            .breakpoint_store()
20238            .as_ref()
20239            .unwrap()
20240            .read(cx)
20241            .all_source_breakpoints(cx)
20242            .clone()
20243    });
20244
20245    assert_eq!(1, breakpoints.len());
20246    assert_breakpoint(
20247        &breakpoints,
20248        &abs_path,
20249        vec![(3, Breakpoint::new_standard())],
20250    );
20251
20252    editor.update_in(cx, |editor, window, cx| {
20253        editor.move_to_end(&MoveToEnd, window, cx);
20254        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20255    });
20256
20257    let breakpoints = editor.update(cx, |editor, cx| {
20258        editor
20259            .breakpoint_store()
20260            .as_ref()
20261            .unwrap()
20262            .read(cx)
20263            .all_source_breakpoints(cx)
20264            .clone()
20265    });
20266
20267    assert_eq!(0, breakpoints.len());
20268    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20269}
20270
20271#[gpui::test]
20272async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20273    init_test(cx, |_| {});
20274
20275    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20276
20277    let fs = FakeFs::new(cx.executor());
20278    fs.insert_tree(
20279        path!("/a"),
20280        json!({
20281            "main.rs": sample_text,
20282        }),
20283    )
20284    .await;
20285    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20286    let (workspace, cx) =
20287        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20288
20289    let worktree_id = workspace.update(cx, |workspace, cx| {
20290        workspace.project().update(cx, |project, cx| {
20291            project.worktrees(cx).next().unwrap().read(cx).id()
20292        })
20293    });
20294
20295    let buffer = project
20296        .update(cx, |project, cx| {
20297            project.open_buffer((worktree_id, "main.rs"), cx)
20298        })
20299        .await
20300        .unwrap();
20301
20302    let (editor, cx) = cx.add_window_view(|window, cx| {
20303        Editor::new(
20304            EditorMode::full(),
20305            MultiBuffer::build_from_buffer(buffer, cx),
20306            Some(project.clone()),
20307            window,
20308            cx,
20309        )
20310    });
20311
20312    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20313    let abs_path = project.read_with(cx, |project, cx| {
20314        project
20315            .absolute_path(&project_path, cx)
20316            .map(|path_buf| Arc::from(path_buf.to_owned()))
20317            .unwrap()
20318    });
20319
20320    editor.update_in(cx, |editor, window, cx| {
20321        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20322    });
20323
20324    let breakpoints = editor.update(cx, |editor, cx| {
20325        editor
20326            .breakpoint_store()
20327            .as_ref()
20328            .unwrap()
20329            .read(cx)
20330            .all_source_breakpoints(cx)
20331            .clone()
20332    });
20333
20334    assert_breakpoint(
20335        &breakpoints,
20336        &abs_path,
20337        vec![(0, Breakpoint::new_log("hello world"))],
20338    );
20339
20340    // Removing a log message from a log breakpoint should remove it
20341    editor.update_in(cx, |editor, window, cx| {
20342        add_log_breakpoint_at_cursor(editor, "", window, cx);
20343    });
20344
20345    let breakpoints = editor.update(cx, |editor, cx| {
20346        editor
20347            .breakpoint_store()
20348            .as_ref()
20349            .unwrap()
20350            .read(cx)
20351            .all_source_breakpoints(cx)
20352            .clone()
20353    });
20354
20355    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20356
20357    editor.update_in(cx, |editor, window, cx| {
20358        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20359        editor.move_to_end(&MoveToEnd, window, cx);
20360        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20361        // Not adding a log message to a standard breakpoint shouldn't remove it
20362        add_log_breakpoint_at_cursor(editor, "", window, cx);
20363    });
20364
20365    let breakpoints = editor.update(cx, |editor, cx| {
20366        editor
20367            .breakpoint_store()
20368            .as_ref()
20369            .unwrap()
20370            .read(cx)
20371            .all_source_breakpoints(cx)
20372            .clone()
20373    });
20374
20375    assert_breakpoint(
20376        &breakpoints,
20377        &abs_path,
20378        vec![
20379            (0, Breakpoint::new_standard()),
20380            (3, Breakpoint::new_standard()),
20381        ],
20382    );
20383
20384    editor.update_in(cx, |editor, window, cx| {
20385        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20386    });
20387
20388    let breakpoints = editor.update(cx, |editor, cx| {
20389        editor
20390            .breakpoint_store()
20391            .as_ref()
20392            .unwrap()
20393            .read(cx)
20394            .all_source_breakpoints(cx)
20395            .clone()
20396    });
20397
20398    assert_breakpoint(
20399        &breakpoints,
20400        &abs_path,
20401        vec![
20402            (0, Breakpoint::new_standard()),
20403            (3, Breakpoint::new_log("hello world")),
20404        ],
20405    );
20406
20407    editor.update_in(cx, |editor, window, cx| {
20408        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20409    });
20410
20411    let breakpoints = editor.update(cx, |editor, cx| {
20412        editor
20413            .breakpoint_store()
20414            .as_ref()
20415            .unwrap()
20416            .read(cx)
20417            .all_source_breakpoints(cx)
20418            .clone()
20419    });
20420
20421    assert_breakpoint(
20422        &breakpoints,
20423        &abs_path,
20424        vec![
20425            (0, Breakpoint::new_standard()),
20426            (3, Breakpoint::new_log("hello Earth!!")),
20427        ],
20428    );
20429}
20430
20431/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20432/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20433/// or when breakpoints were placed out of order. This tests for a regression too
20434#[gpui::test]
20435async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20436    init_test(cx, |_| {});
20437
20438    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20439    let fs = FakeFs::new(cx.executor());
20440    fs.insert_tree(
20441        path!("/a"),
20442        json!({
20443            "main.rs": sample_text,
20444        }),
20445    )
20446    .await;
20447    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20448    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20449    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20450
20451    let fs = FakeFs::new(cx.executor());
20452    fs.insert_tree(
20453        path!("/a"),
20454        json!({
20455            "main.rs": sample_text,
20456        }),
20457    )
20458    .await;
20459    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20460    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20461    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20462    let worktree_id = workspace
20463        .update(cx, |workspace, _window, cx| {
20464            workspace.project().update(cx, |project, cx| {
20465                project.worktrees(cx).next().unwrap().read(cx).id()
20466            })
20467        })
20468        .unwrap();
20469
20470    let buffer = project
20471        .update(cx, |project, cx| {
20472            project.open_buffer((worktree_id, "main.rs"), cx)
20473        })
20474        .await
20475        .unwrap();
20476
20477    let (editor, cx) = cx.add_window_view(|window, cx| {
20478        Editor::new(
20479            EditorMode::full(),
20480            MultiBuffer::build_from_buffer(buffer, cx),
20481            Some(project.clone()),
20482            window,
20483            cx,
20484        )
20485    });
20486
20487    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20488    let abs_path = project.read_with(cx, |project, cx| {
20489        project
20490            .absolute_path(&project_path, cx)
20491            .map(|path_buf| Arc::from(path_buf.to_owned()))
20492            .unwrap()
20493    });
20494
20495    // assert we can add breakpoint on the first line
20496    editor.update_in(cx, |editor, window, cx| {
20497        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20498        editor.move_to_end(&MoveToEnd, window, cx);
20499        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20500        editor.move_up(&MoveUp, window, cx);
20501        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20502    });
20503
20504    let breakpoints = editor.update(cx, |editor, cx| {
20505        editor
20506            .breakpoint_store()
20507            .as_ref()
20508            .unwrap()
20509            .read(cx)
20510            .all_source_breakpoints(cx)
20511            .clone()
20512    });
20513
20514    assert_eq!(1, breakpoints.len());
20515    assert_breakpoint(
20516        &breakpoints,
20517        &abs_path,
20518        vec![
20519            (0, Breakpoint::new_standard()),
20520            (2, Breakpoint::new_standard()),
20521            (3, Breakpoint::new_standard()),
20522        ],
20523    );
20524
20525    editor.update_in(cx, |editor, window, cx| {
20526        editor.move_to_beginning(&MoveToBeginning, window, cx);
20527        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20528        editor.move_to_end(&MoveToEnd, window, cx);
20529        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20530        // Disabling a breakpoint that doesn't exist should do nothing
20531        editor.move_up(&MoveUp, window, cx);
20532        editor.move_up(&MoveUp, window, cx);
20533        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20534    });
20535
20536    let breakpoints = editor.update(cx, |editor, cx| {
20537        editor
20538            .breakpoint_store()
20539            .as_ref()
20540            .unwrap()
20541            .read(cx)
20542            .all_source_breakpoints(cx)
20543            .clone()
20544    });
20545
20546    let disable_breakpoint = {
20547        let mut bp = Breakpoint::new_standard();
20548        bp.state = BreakpointState::Disabled;
20549        bp
20550    };
20551
20552    assert_eq!(1, breakpoints.len());
20553    assert_breakpoint(
20554        &breakpoints,
20555        &abs_path,
20556        vec![
20557            (0, disable_breakpoint.clone()),
20558            (2, Breakpoint::new_standard()),
20559            (3, disable_breakpoint.clone()),
20560        ],
20561    );
20562
20563    editor.update_in(cx, |editor, window, cx| {
20564        editor.move_to_beginning(&MoveToBeginning, window, cx);
20565        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20566        editor.move_to_end(&MoveToEnd, window, cx);
20567        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20568        editor.move_up(&MoveUp, window, cx);
20569        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20570    });
20571
20572    let breakpoints = editor.update(cx, |editor, cx| {
20573        editor
20574            .breakpoint_store()
20575            .as_ref()
20576            .unwrap()
20577            .read(cx)
20578            .all_source_breakpoints(cx)
20579            .clone()
20580    });
20581
20582    assert_eq!(1, breakpoints.len());
20583    assert_breakpoint(
20584        &breakpoints,
20585        &abs_path,
20586        vec![
20587            (0, Breakpoint::new_standard()),
20588            (2, disable_breakpoint),
20589            (3, Breakpoint::new_standard()),
20590        ],
20591    );
20592}
20593
20594#[gpui::test]
20595async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20596    init_test(cx, |_| {});
20597    let capabilities = lsp::ServerCapabilities {
20598        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20599            prepare_provider: Some(true),
20600            work_done_progress_options: Default::default(),
20601        })),
20602        ..Default::default()
20603    };
20604    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20605
20606    cx.set_state(indoc! {"
20607        struct Fˇoo {}
20608    "});
20609
20610    cx.update_editor(|editor, _, cx| {
20611        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20612        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20613        editor.highlight_background::<DocumentHighlightRead>(
20614            &[highlight_range],
20615            |theme| theme.colors().editor_document_highlight_read_background,
20616            cx,
20617        );
20618    });
20619
20620    let mut prepare_rename_handler = cx
20621        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20622            move |_, _, _| async move {
20623                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20624                    start: lsp::Position {
20625                        line: 0,
20626                        character: 7,
20627                    },
20628                    end: lsp::Position {
20629                        line: 0,
20630                        character: 10,
20631                    },
20632                })))
20633            },
20634        );
20635    let prepare_rename_task = cx
20636        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20637        .expect("Prepare rename was not started");
20638    prepare_rename_handler.next().await.unwrap();
20639    prepare_rename_task.await.expect("Prepare rename failed");
20640
20641    let mut rename_handler =
20642        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20643            let edit = lsp::TextEdit {
20644                range: lsp::Range {
20645                    start: lsp::Position {
20646                        line: 0,
20647                        character: 7,
20648                    },
20649                    end: lsp::Position {
20650                        line: 0,
20651                        character: 10,
20652                    },
20653                },
20654                new_text: "FooRenamed".to_string(),
20655            };
20656            Ok(Some(lsp::WorkspaceEdit::new(
20657                // Specify the same edit twice
20658                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20659            )))
20660        });
20661    let rename_task = cx
20662        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20663        .expect("Confirm rename was not started");
20664    rename_handler.next().await.unwrap();
20665    rename_task.await.expect("Confirm rename failed");
20666    cx.run_until_parked();
20667
20668    // Despite two edits, only one is actually applied as those are identical
20669    cx.assert_editor_state(indoc! {"
20670        struct FooRenamedˇ {}
20671    "});
20672}
20673
20674#[gpui::test]
20675async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20676    init_test(cx, |_| {});
20677    // These capabilities indicate that the server does not support prepare rename.
20678    let capabilities = lsp::ServerCapabilities {
20679        rename_provider: Some(lsp::OneOf::Left(true)),
20680        ..Default::default()
20681    };
20682    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20683
20684    cx.set_state(indoc! {"
20685        struct Fˇoo {}
20686    "});
20687
20688    cx.update_editor(|editor, _window, cx| {
20689        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20690        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20691        editor.highlight_background::<DocumentHighlightRead>(
20692            &[highlight_range],
20693            |theme| theme.colors().editor_document_highlight_read_background,
20694            cx,
20695        );
20696    });
20697
20698    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20699        .expect("Prepare rename was not started")
20700        .await
20701        .expect("Prepare rename failed");
20702
20703    let mut rename_handler =
20704        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20705            let edit = lsp::TextEdit {
20706                range: lsp::Range {
20707                    start: lsp::Position {
20708                        line: 0,
20709                        character: 7,
20710                    },
20711                    end: lsp::Position {
20712                        line: 0,
20713                        character: 10,
20714                    },
20715                },
20716                new_text: "FooRenamed".to_string(),
20717            };
20718            Ok(Some(lsp::WorkspaceEdit::new(
20719                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20720            )))
20721        });
20722    let rename_task = cx
20723        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20724        .expect("Confirm rename was not started");
20725    rename_handler.next().await.unwrap();
20726    rename_task.await.expect("Confirm rename failed");
20727    cx.run_until_parked();
20728
20729    // Correct range is renamed, as `surrounding_word` is used to find it.
20730    cx.assert_editor_state(indoc! {"
20731        struct FooRenamedˇ {}
20732    "});
20733}
20734
20735#[gpui::test]
20736async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20737    init_test(cx, |_| {});
20738    let mut cx = EditorTestContext::new(cx).await;
20739
20740    let language = Arc::new(
20741        Language::new(
20742            LanguageConfig::default(),
20743            Some(tree_sitter_html::LANGUAGE.into()),
20744        )
20745        .with_brackets_query(
20746            r#"
20747            ("<" @open "/>" @close)
20748            ("</" @open ">" @close)
20749            ("<" @open ">" @close)
20750            ("\"" @open "\"" @close)
20751            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20752        "#,
20753        )
20754        .unwrap(),
20755    );
20756    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20757
20758    cx.set_state(indoc! {"
20759        <span>ˇ</span>
20760    "});
20761    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20762    cx.assert_editor_state(indoc! {"
20763        <span>
20764        ˇ
20765        </span>
20766    "});
20767
20768    cx.set_state(indoc! {"
20769        <span><span></span>ˇ</span>
20770    "});
20771    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20772    cx.assert_editor_state(indoc! {"
20773        <span><span></span>
20774        ˇ</span>
20775    "});
20776
20777    cx.set_state(indoc! {"
20778        <span>ˇ
20779        </span>
20780    "});
20781    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20782    cx.assert_editor_state(indoc! {"
20783        <span>
20784        ˇ
20785        </span>
20786    "});
20787}
20788
20789#[gpui::test(iterations = 10)]
20790async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20791    init_test(cx, |_| {});
20792
20793    let fs = FakeFs::new(cx.executor());
20794    fs.insert_tree(
20795        path!("/dir"),
20796        json!({
20797            "a.ts": "a",
20798        }),
20799    )
20800    .await;
20801
20802    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20803    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20804    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20805
20806    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20807    language_registry.add(Arc::new(Language::new(
20808        LanguageConfig {
20809            name: "TypeScript".into(),
20810            matcher: LanguageMatcher {
20811                path_suffixes: vec!["ts".to_string()],
20812                ..Default::default()
20813            },
20814            ..Default::default()
20815        },
20816        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20817    )));
20818    let mut fake_language_servers = language_registry.register_fake_lsp(
20819        "TypeScript",
20820        FakeLspAdapter {
20821            capabilities: lsp::ServerCapabilities {
20822                code_lens_provider: Some(lsp::CodeLensOptions {
20823                    resolve_provider: Some(true),
20824                }),
20825                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20826                    commands: vec!["_the/command".to_string()],
20827                    ..lsp::ExecuteCommandOptions::default()
20828                }),
20829                ..lsp::ServerCapabilities::default()
20830            },
20831            ..FakeLspAdapter::default()
20832        },
20833    );
20834
20835    let (buffer, _handle) = project
20836        .update(cx, |p, cx| {
20837            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20838        })
20839        .await
20840        .unwrap();
20841    cx.executor().run_until_parked();
20842
20843    let fake_server = fake_language_servers.next().await.unwrap();
20844
20845    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20846    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20847    drop(buffer_snapshot);
20848    let actions = cx
20849        .update_window(*workspace, |_, window, cx| {
20850            project.code_actions(&buffer, anchor..anchor, window, cx)
20851        })
20852        .unwrap();
20853
20854    fake_server
20855        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20856            Ok(Some(vec![
20857                lsp::CodeLens {
20858                    range: lsp::Range::default(),
20859                    command: Some(lsp::Command {
20860                        title: "Code lens command".to_owned(),
20861                        command: "_the/command".to_owned(),
20862                        arguments: None,
20863                    }),
20864                    data: None,
20865                },
20866                lsp::CodeLens {
20867                    range: lsp::Range::default(),
20868                    command: Some(lsp::Command {
20869                        title: "Command not in capabilities".to_owned(),
20870                        command: "not in capabilities".to_owned(),
20871                        arguments: None,
20872                    }),
20873                    data: None,
20874                },
20875                lsp::CodeLens {
20876                    range: lsp::Range {
20877                        start: lsp::Position {
20878                            line: 1,
20879                            character: 1,
20880                        },
20881                        end: lsp::Position {
20882                            line: 1,
20883                            character: 1,
20884                        },
20885                    },
20886                    command: Some(lsp::Command {
20887                        title: "Command not in range".to_owned(),
20888                        command: "_the/command".to_owned(),
20889                        arguments: None,
20890                    }),
20891                    data: None,
20892                },
20893            ]))
20894        })
20895        .next()
20896        .await;
20897
20898    let actions = actions.await.unwrap();
20899    assert_eq!(
20900        actions.len(),
20901        1,
20902        "Should have only one valid action for the 0..0 range"
20903    );
20904    let action = actions[0].clone();
20905    let apply = project.update(cx, |project, cx| {
20906        project.apply_code_action(buffer.clone(), action, true, cx)
20907    });
20908
20909    // Resolving the code action does not populate its edits. In absence of
20910    // edits, we must execute the given command.
20911    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20912        |mut lens, _| async move {
20913            let lens_command = lens.command.as_mut().expect("should have a command");
20914            assert_eq!(lens_command.title, "Code lens command");
20915            lens_command.arguments = Some(vec![json!("the-argument")]);
20916            Ok(lens)
20917        },
20918    );
20919
20920    // While executing the command, the language server sends the editor
20921    // a `workspaceEdit` request.
20922    fake_server
20923        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20924            let fake = fake_server.clone();
20925            move |params, _| {
20926                assert_eq!(params.command, "_the/command");
20927                let fake = fake.clone();
20928                async move {
20929                    fake.server
20930                        .request::<lsp::request::ApplyWorkspaceEdit>(
20931                            lsp::ApplyWorkspaceEditParams {
20932                                label: None,
20933                                edit: lsp::WorkspaceEdit {
20934                                    changes: Some(
20935                                        [(
20936                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20937                                            vec![lsp::TextEdit {
20938                                                range: lsp::Range::new(
20939                                                    lsp::Position::new(0, 0),
20940                                                    lsp::Position::new(0, 0),
20941                                                ),
20942                                                new_text: "X".into(),
20943                                            }],
20944                                        )]
20945                                        .into_iter()
20946                                        .collect(),
20947                                    ),
20948                                    ..Default::default()
20949                                },
20950                            },
20951                        )
20952                        .await
20953                        .into_response()
20954                        .unwrap();
20955                    Ok(Some(json!(null)))
20956                }
20957            }
20958        })
20959        .next()
20960        .await;
20961
20962    // Applying the code lens command returns a project transaction containing the edits
20963    // sent by the language server in its `workspaceEdit` request.
20964    let transaction = apply.await.unwrap();
20965    assert!(transaction.0.contains_key(&buffer));
20966    buffer.update(cx, |buffer, cx| {
20967        assert_eq!(buffer.text(), "Xa");
20968        buffer.undo(cx);
20969        assert_eq!(buffer.text(), "a");
20970    });
20971}
20972
20973#[gpui::test]
20974async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20975    init_test(cx, |_| {});
20976
20977    let fs = FakeFs::new(cx.executor());
20978    let main_text = r#"fn main() {
20979println!("1");
20980println!("2");
20981println!("3");
20982println!("4");
20983println!("5");
20984}"#;
20985    let lib_text = "mod foo {}";
20986    fs.insert_tree(
20987        path!("/a"),
20988        json!({
20989            "lib.rs": lib_text,
20990            "main.rs": main_text,
20991        }),
20992    )
20993    .await;
20994
20995    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20996    let (workspace, cx) =
20997        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20998    let worktree_id = workspace.update(cx, |workspace, cx| {
20999        workspace.project().update(cx, |project, cx| {
21000            project.worktrees(cx).next().unwrap().read(cx).id()
21001        })
21002    });
21003
21004    let expected_ranges = vec![
21005        Point::new(0, 0)..Point::new(0, 0),
21006        Point::new(1, 0)..Point::new(1, 1),
21007        Point::new(2, 0)..Point::new(2, 2),
21008        Point::new(3, 0)..Point::new(3, 3),
21009    ];
21010
21011    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21012    let editor_1 = workspace
21013        .update_in(cx, |workspace, window, cx| {
21014            workspace.open_path(
21015                (worktree_id, "main.rs"),
21016                Some(pane_1.downgrade()),
21017                true,
21018                window,
21019                cx,
21020            )
21021        })
21022        .unwrap()
21023        .await
21024        .downcast::<Editor>()
21025        .unwrap();
21026    pane_1.update(cx, |pane, cx| {
21027        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21028        open_editor.update(cx, |editor, cx| {
21029            assert_eq!(
21030                editor.display_text(cx),
21031                main_text,
21032                "Original main.rs text on initial open",
21033            );
21034            assert_eq!(
21035                editor
21036                    .selections
21037                    .all::<Point>(cx)
21038                    .into_iter()
21039                    .map(|s| s.range())
21040                    .collect::<Vec<_>>(),
21041                vec![Point::zero()..Point::zero()],
21042                "Default selections on initial open",
21043            );
21044        })
21045    });
21046    editor_1.update_in(cx, |editor, window, cx| {
21047        editor.change_selections(None, window, cx, |s| {
21048            s.select_ranges(expected_ranges.clone());
21049        });
21050    });
21051
21052    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21053        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21054    });
21055    let editor_2 = workspace
21056        .update_in(cx, |workspace, window, cx| {
21057            workspace.open_path(
21058                (worktree_id, "main.rs"),
21059                Some(pane_2.downgrade()),
21060                true,
21061                window,
21062                cx,
21063            )
21064        })
21065        .unwrap()
21066        .await
21067        .downcast::<Editor>()
21068        .unwrap();
21069    pane_2.update(cx, |pane, cx| {
21070        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21071        open_editor.update(cx, |editor, cx| {
21072            assert_eq!(
21073                editor.display_text(cx),
21074                main_text,
21075                "Original main.rs text on initial open in another panel",
21076            );
21077            assert_eq!(
21078                editor
21079                    .selections
21080                    .all::<Point>(cx)
21081                    .into_iter()
21082                    .map(|s| s.range())
21083                    .collect::<Vec<_>>(),
21084                vec![Point::zero()..Point::zero()],
21085                "Default selections on initial open in another panel",
21086            );
21087        })
21088    });
21089
21090    editor_2.update_in(cx, |editor, window, cx| {
21091        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21092    });
21093
21094    let _other_editor_1 = workspace
21095        .update_in(cx, |workspace, window, cx| {
21096            workspace.open_path(
21097                (worktree_id, "lib.rs"),
21098                Some(pane_1.downgrade()),
21099                true,
21100                window,
21101                cx,
21102            )
21103        })
21104        .unwrap()
21105        .await
21106        .downcast::<Editor>()
21107        .unwrap();
21108    pane_1
21109        .update_in(cx, |pane, window, cx| {
21110            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21111        })
21112        .await
21113        .unwrap();
21114    drop(editor_1);
21115    pane_1.update(cx, |pane, cx| {
21116        pane.active_item()
21117            .unwrap()
21118            .downcast::<Editor>()
21119            .unwrap()
21120            .update(cx, |editor, cx| {
21121                assert_eq!(
21122                    editor.display_text(cx),
21123                    lib_text,
21124                    "Other file should be open and active",
21125                );
21126            });
21127        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21128    });
21129
21130    let _other_editor_2 = workspace
21131        .update_in(cx, |workspace, window, cx| {
21132            workspace.open_path(
21133                (worktree_id, "lib.rs"),
21134                Some(pane_2.downgrade()),
21135                true,
21136                window,
21137                cx,
21138            )
21139        })
21140        .unwrap()
21141        .await
21142        .downcast::<Editor>()
21143        .unwrap();
21144    pane_2
21145        .update_in(cx, |pane, window, cx| {
21146            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21147        })
21148        .await
21149        .unwrap();
21150    drop(editor_2);
21151    pane_2.update(cx, |pane, cx| {
21152        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21153        open_editor.update(cx, |editor, cx| {
21154            assert_eq!(
21155                editor.display_text(cx),
21156                lib_text,
21157                "Other file should be open and active in another panel too",
21158            );
21159        });
21160        assert_eq!(
21161            pane.items().count(),
21162            1,
21163            "No other editors should be open in another pane",
21164        );
21165    });
21166
21167    let _editor_1_reopened = workspace
21168        .update_in(cx, |workspace, window, cx| {
21169            workspace.open_path(
21170                (worktree_id, "main.rs"),
21171                Some(pane_1.downgrade()),
21172                true,
21173                window,
21174                cx,
21175            )
21176        })
21177        .unwrap()
21178        .await
21179        .downcast::<Editor>()
21180        .unwrap();
21181    let _editor_2_reopened = workspace
21182        .update_in(cx, |workspace, window, cx| {
21183            workspace.open_path(
21184                (worktree_id, "main.rs"),
21185                Some(pane_2.downgrade()),
21186                true,
21187                window,
21188                cx,
21189            )
21190        })
21191        .unwrap()
21192        .await
21193        .downcast::<Editor>()
21194        .unwrap();
21195    pane_1.update(cx, |pane, cx| {
21196        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21197        open_editor.update(cx, |editor, cx| {
21198            assert_eq!(
21199                editor.display_text(cx),
21200                main_text,
21201                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21202            );
21203            assert_eq!(
21204                editor
21205                    .selections
21206                    .all::<Point>(cx)
21207                    .into_iter()
21208                    .map(|s| s.range())
21209                    .collect::<Vec<_>>(),
21210                expected_ranges,
21211                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21212            );
21213        })
21214    });
21215    pane_2.update(cx, |pane, cx| {
21216        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21217        open_editor.update(cx, |editor, cx| {
21218            assert_eq!(
21219                editor.display_text(cx),
21220                r#"fn main() {
21221⋯rintln!("1");
21222⋯intln!("2");
21223⋯ntln!("3");
21224println!("4");
21225println!("5");
21226}"#,
21227                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21228            );
21229            assert_eq!(
21230                editor
21231                    .selections
21232                    .all::<Point>(cx)
21233                    .into_iter()
21234                    .map(|s| s.range())
21235                    .collect::<Vec<_>>(),
21236                vec![Point::zero()..Point::zero()],
21237                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21238            );
21239        })
21240    });
21241}
21242
21243#[gpui::test]
21244async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21245    init_test(cx, |_| {});
21246
21247    let fs = FakeFs::new(cx.executor());
21248    let main_text = r#"fn main() {
21249println!("1");
21250println!("2");
21251println!("3");
21252println!("4");
21253println!("5");
21254}"#;
21255    let lib_text = "mod foo {}";
21256    fs.insert_tree(
21257        path!("/a"),
21258        json!({
21259            "lib.rs": lib_text,
21260            "main.rs": main_text,
21261        }),
21262    )
21263    .await;
21264
21265    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21266    let (workspace, cx) =
21267        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21268    let worktree_id = workspace.update(cx, |workspace, cx| {
21269        workspace.project().update(cx, |project, cx| {
21270            project.worktrees(cx).next().unwrap().read(cx).id()
21271        })
21272    });
21273
21274    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21275    let editor = workspace
21276        .update_in(cx, |workspace, window, cx| {
21277            workspace.open_path(
21278                (worktree_id, "main.rs"),
21279                Some(pane.downgrade()),
21280                true,
21281                window,
21282                cx,
21283            )
21284        })
21285        .unwrap()
21286        .await
21287        .downcast::<Editor>()
21288        .unwrap();
21289    pane.update(cx, |pane, cx| {
21290        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21291        open_editor.update(cx, |editor, cx| {
21292            assert_eq!(
21293                editor.display_text(cx),
21294                main_text,
21295                "Original main.rs text on initial open",
21296            );
21297        })
21298    });
21299    editor.update_in(cx, |editor, window, cx| {
21300        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21301    });
21302
21303    cx.update_global(|store: &mut SettingsStore, cx| {
21304        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21305            s.restore_on_file_reopen = Some(false);
21306        });
21307    });
21308    editor.update_in(cx, |editor, window, cx| {
21309        editor.fold_ranges(
21310            vec![
21311                Point::new(1, 0)..Point::new(1, 1),
21312                Point::new(2, 0)..Point::new(2, 2),
21313                Point::new(3, 0)..Point::new(3, 3),
21314            ],
21315            false,
21316            window,
21317            cx,
21318        );
21319    });
21320    pane.update_in(cx, |pane, window, cx| {
21321        pane.close_all_items(&CloseAllItems::default(), window, cx)
21322    })
21323    .await
21324    .unwrap();
21325    pane.update(cx, |pane, _| {
21326        assert!(pane.active_item().is_none());
21327    });
21328    cx.update_global(|store: &mut SettingsStore, cx| {
21329        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21330            s.restore_on_file_reopen = Some(true);
21331        });
21332    });
21333
21334    let _editor_reopened = workspace
21335        .update_in(cx, |workspace, window, cx| {
21336            workspace.open_path(
21337                (worktree_id, "main.rs"),
21338                Some(pane.downgrade()),
21339                true,
21340                window,
21341                cx,
21342            )
21343        })
21344        .unwrap()
21345        .await
21346        .downcast::<Editor>()
21347        .unwrap();
21348    pane.update(cx, |pane, cx| {
21349        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21350        open_editor.update(cx, |editor, cx| {
21351            assert_eq!(
21352                editor.display_text(cx),
21353                main_text,
21354                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21355            );
21356        })
21357    });
21358}
21359
21360#[gpui::test]
21361async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21362    struct EmptyModalView {
21363        focus_handle: gpui::FocusHandle,
21364    }
21365    impl EventEmitter<DismissEvent> for EmptyModalView {}
21366    impl Render for EmptyModalView {
21367        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21368            div()
21369        }
21370    }
21371    impl Focusable for EmptyModalView {
21372        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21373            self.focus_handle.clone()
21374        }
21375    }
21376    impl workspace::ModalView for EmptyModalView {}
21377    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21378        EmptyModalView {
21379            focus_handle: cx.focus_handle(),
21380        }
21381    }
21382
21383    init_test(cx, |_| {});
21384
21385    let fs = FakeFs::new(cx.executor());
21386    let project = Project::test(fs, [], cx).await;
21387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21388    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21389    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21390    let editor = cx.new_window_entity(|window, cx| {
21391        Editor::new(
21392            EditorMode::full(),
21393            buffer,
21394            Some(project.clone()),
21395            window,
21396            cx,
21397        )
21398    });
21399    workspace
21400        .update(cx, |workspace, window, cx| {
21401            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21402        })
21403        .unwrap();
21404    editor.update_in(cx, |editor, window, cx| {
21405        editor.open_context_menu(&OpenContextMenu, window, cx);
21406        assert!(editor.mouse_context_menu.is_some());
21407    });
21408    workspace
21409        .update(cx, |workspace, window, cx| {
21410            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21411        })
21412        .unwrap();
21413    cx.read(|cx| {
21414        assert!(editor.read(cx).mouse_context_menu.is_none());
21415    });
21416}
21417
21418#[gpui::test]
21419async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21420    init_test(cx, |_| {});
21421
21422    let fs = FakeFs::new(cx.executor());
21423    fs.insert_file(path!("/file.html"), Default::default())
21424        .await;
21425
21426    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21427
21428    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21429    let html_language = Arc::new(Language::new(
21430        LanguageConfig {
21431            name: "HTML".into(),
21432            matcher: LanguageMatcher {
21433                path_suffixes: vec!["html".to_string()],
21434                ..LanguageMatcher::default()
21435            },
21436            brackets: BracketPairConfig {
21437                pairs: vec![BracketPair {
21438                    start: "<".into(),
21439                    end: ">".into(),
21440                    close: true,
21441                    ..Default::default()
21442                }],
21443                ..Default::default()
21444            },
21445            ..Default::default()
21446        },
21447        Some(tree_sitter_html::LANGUAGE.into()),
21448    ));
21449    language_registry.add(html_language);
21450    let mut fake_servers = language_registry.register_fake_lsp(
21451        "HTML",
21452        FakeLspAdapter {
21453            capabilities: lsp::ServerCapabilities {
21454                completion_provider: Some(lsp::CompletionOptions {
21455                    resolve_provider: Some(true),
21456                    ..Default::default()
21457                }),
21458                ..Default::default()
21459            },
21460            ..Default::default()
21461        },
21462    );
21463
21464    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21465    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21466
21467    let worktree_id = workspace
21468        .update(cx, |workspace, _window, cx| {
21469            workspace.project().update(cx, |project, cx| {
21470                project.worktrees(cx).next().unwrap().read(cx).id()
21471            })
21472        })
21473        .unwrap();
21474    project
21475        .update(cx, |project, cx| {
21476            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21477        })
21478        .await
21479        .unwrap();
21480    let editor = workspace
21481        .update(cx, |workspace, window, cx| {
21482            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21483        })
21484        .unwrap()
21485        .await
21486        .unwrap()
21487        .downcast::<Editor>()
21488        .unwrap();
21489
21490    let fake_server = fake_servers.next().await.unwrap();
21491    editor.update_in(cx, |editor, window, cx| {
21492        editor.set_text("<ad></ad>", window, cx);
21493        editor.change_selections(None, window, cx, |selections| {
21494            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21495        });
21496        let Some((buffer, _)) = editor
21497            .buffer
21498            .read(cx)
21499            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21500        else {
21501            panic!("Failed to get buffer for selection position");
21502        };
21503        let buffer = buffer.read(cx);
21504        let buffer_id = buffer.remote_id();
21505        let opening_range =
21506            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21507        let closing_range =
21508            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21509        let mut linked_ranges = HashMap::default();
21510        linked_ranges.insert(
21511            buffer_id,
21512            vec![(opening_range.clone(), vec![closing_range.clone()])],
21513        );
21514        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21515    });
21516    let mut completion_handle =
21517        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21518            Ok(Some(lsp::CompletionResponse::Array(vec![
21519                lsp::CompletionItem {
21520                    label: "head".to_string(),
21521                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21522                        lsp::InsertReplaceEdit {
21523                            new_text: "head".to_string(),
21524                            insert: lsp::Range::new(
21525                                lsp::Position::new(0, 1),
21526                                lsp::Position::new(0, 3),
21527                            ),
21528                            replace: lsp::Range::new(
21529                                lsp::Position::new(0, 1),
21530                                lsp::Position::new(0, 3),
21531                            ),
21532                        },
21533                    )),
21534                    ..Default::default()
21535                },
21536            ])))
21537        });
21538    editor.update_in(cx, |editor, window, cx| {
21539        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21540    });
21541    cx.run_until_parked();
21542    completion_handle.next().await.unwrap();
21543    editor.update(cx, |editor, _| {
21544        assert!(
21545            editor.context_menu_visible(),
21546            "Completion menu should be visible"
21547        );
21548    });
21549    editor.update_in(cx, |editor, window, cx| {
21550        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21551    });
21552    cx.executor().run_until_parked();
21553    editor.update(cx, |editor, cx| {
21554        assert_eq!(editor.text(cx), "<head></head>");
21555    });
21556}
21557
21558#[gpui::test]
21559async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21560    init_test(cx, |_| {});
21561
21562    let fs = FakeFs::new(cx.executor());
21563    fs.insert_tree(
21564        path!("/root"),
21565        json!({
21566            "a": {
21567                "main.rs": "fn main() {}",
21568            },
21569            "foo": {
21570                "bar": {
21571                    "external_file.rs": "pub mod external {}",
21572                }
21573            }
21574        }),
21575    )
21576    .await;
21577
21578    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21579    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21580    language_registry.add(rust_lang());
21581    let _fake_servers = language_registry.register_fake_lsp(
21582        "Rust",
21583        FakeLspAdapter {
21584            ..FakeLspAdapter::default()
21585        },
21586    );
21587    let (workspace, cx) =
21588        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21589    let worktree_id = workspace.update(cx, |workspace, cx| {
21590        workspace.project().update(cx, |project, cx| {
21591            project.worktrees(cx).next().unwrap().read(cx).id()
21592        })
21593    });
21594
21595    let assert_language_servers_count =
21596        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21597            project.update(cx, |project, cx| {
21598                let current = project
21599                    .lsp_store()
21600                    .read(cx)
21601                    .as_local()
21602                    .unwrap()
21603                    .language_servers
21604                    .len();
21605                assert_eq!(expected, current, "{context}");
21606            });
21607        };
21608
21609    assert_language_servers_count(
21610        0,
21611        "No servers should be running before any file is open",
21612        cx,
21613    );
21614    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21615    let main_editor = workspace
21616        .update_in(cx, |workspace, window, cx| {
21617            workspace.open_path(
21618                (worktree_id, "main.rs"),
21619                Some(pane.downgrade()),
21620                true,
21621                window,
21622                cx,
21623            )
21624        })
21625        .unwrap()
21626        .await
21627        .downcast::<Editor>()
21628        .unwrap();
21629    pane.update(cx, |pane, cx| {
21630        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21631        open_editor.update(cx, |editor, cx| {
21632            assert_eq!(
21633                editor.display_text(cx),
21634                "fn main() {}",
21635                "Original main.rs text on initial open",
21636            );
21637        });
21638        assert_eq!(open_editor, main_editor);
21639    });
21640    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21641
21642    let external_editor = workspace
21643        .update_in(cx, |workspace, window, cx| {
21644            workspace.open_abs_path(
21645                PathBuf::from("/root/foo/bar/external_file.rs"),
21646                OpenOptions::default(),
21647                window,
21648                cx,
21649            )
21650        })
21651        .await
21652        .expect("opening external file")
21653        .downcast::<Editor>()
21654        .expect("downcasted external file's open element to editor");
21655    pane.update(cx, |pane, cx| {
21656        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21657        open_editor.update(cx, |editor, cx| {
21658            assert_eq!(
21659                editor.display_text(cx),
21660                "pub mod external {}",
21661                "External file is open now",
21662            );
21663        });
21664        assert_eq!(open_editor, external_editor);
21665    });
21666    assert_language_servers_count(
21667        1,
21668        "Second, external, *.rs file should join the existing server",
21669        cx,
21670    );
21671
21672    pane.update_in(cx, |pane, window, cx| {
21673        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21674    })
21675    .await
21676    .unwrap();
21677    pane.update_in(cx, |pane, window, cx| {
21678        pane.navigate_backward(window, cx);
21679    });
21680    cx.run_until_parked();
21681    pane.update(cx, |pane, cx| {
21682        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21683        open_editor.update(cx, |editor, cx| {
21684            assert_eq!(
21685                editor.display_text(cx),
21686                "pub mod external {}",
21687                "External file is open now",
21688            );
21689        });
21690    });
21691    assert_language_servers_count(
21692        1,
21693        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21694        cx,
21695    );
21696
21697    cx.update(|_, cx| {
21698        workspace::reload(&workspace::Reload::default(), cx);
21699    });
21700    assert_language_servers_count(
21701        1,
21702        "After reloading the worktree with local and external files opened, only one project should be started",
21703        cx,
21704    );
21705}
21706
21707#[gpui::test]
21708async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21709    init_test(cx, |_| {});
21710
21711    let mut cx = EditorTestContext::new(cx).await;
21712    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21713    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21714
21715    // test cursor move to start of each line on tab
21716    // for `if`, `elif`, `else`, `while`, `with` and `for`
21717    cx.set_state(indoc! {"
21718        def main():
21719        ˇ    for item in items:
21720        ˇ        while item.active:
21721        ˇ            if item.value > 10:
21722        ˇ                continue
21723        ˇ            elif item.value < 0:
21724        ˇ                break
21725        ˇ            else:
21726        ˇ                with item.context() as ctx:
21727        ˇ                    yield count
21728        ˇ        else:
21729        ˇ            log('while else')
21730        ˇ    else:
21731        ˇ        log('for else')
21732    "});
21733    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21734    cx.assert_editor_state(indoc! {"
21735        def main():
21736            ˇfor item in items:
21737                ˇwhile item.active:
21738                    ˇif item.value > 10:
21739                        ˇcontinue
21740                    ˇelif item.value < 0:
21741                        ˇbreak
21742                    ˇelse:
21743                        ˇwith item.context() as ctx:
21744                            ˇyield count
21745                ˇelse:
21746                    ˇlog('while else')
21747            ˇelse:
21748                ˇlog('for else')
21749    "});
21750    // test relative indent is preserved when tab
21751    // for `if`, `elif`, `else`, `while`, `with` and `for`
21752    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21753    cx.assert_editor_state(indoc! {"
21754        def main():
21755                ˇfor item in items:
21756                    ˇwhile item.active:
21757                        ˇif item.value > 10:
21758                            ˇcontinue
21759                        ˇelif item.value < 0:
21760                            ˇbreak
21761                        ˇelse:
21762                            ˇwith item.context() as ctx:
21763                                ˇyield count
21764                    ˇelse:
21765                        ˇlog('while else')
21766                ˇelse:
21767                    ˇlog('for else')
21768    "});
21769
21770    // test cursor move to start of each line on tab
21771    // for `try`, `except`, `else`, `finally`, `match` and `def`
21772    cx.set_state(indoc! {"
21773        def main():
21774        ˇ    try:
21775        ˇ       fetch()
21776        ˇ    except ValueError:
21777        ˇ       handle_error()
21778        ˇ    else:
21779        ˇ        match value:
21780        ˇ            case _:
21781        ˇ    finally:
21782        ˇ        def status():
21783        ˇ            return 0
21784    "});
21785    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21786    cx.assert_editor_state(indoc! {"
21787        def main():
21788            ˇtry:
21789                ˇfetch()
21790            ˇexcept ValueError:
21791                ˇhandle_error()
21792            ˇelse:
21793                ˇmatch value:
21794                    ˇcase _:
21795            ˇfinally:
21796                ˇdef status():
21797                    ˇreturn 0
21798    "});
21799    // test relative indent is preserved when tab
21800    // for `try`, `except`, `else`, `finally`, `match` and `def`
21801    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21802    cx.assert_editor_state(indoc! {"
21803        def main():
21804                ˇtry:
21805                    ˇfetch()
21806                ˇexcept ValueError:
21807                    ˇhandle_error()
21808                ˇelse:
21809                    ˇmatch value:
21810                        ˇcase _:
21811                ˇfinally:
21812                    ˇdef status():
21813                        ˇreturn 0
21814    "});
21815}
21816
21817#[gpui::test]
21818async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21819    init_test(cx, |_| {});
21820
21821    let mut cx = EditorTestContext::new(cx).await;
21822    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21823    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21824
21825    // test `else` auto outdents when typed inside `if` block
21826    cx.set_state(indoc! {"
21827        def main():
21828            if i == 2:
21829                return
21830                ˇ
21831    "});
21832    cx.update_editor(|editor, window, cx| {
21833        editor.handle_input("else:", window, cx);
21834    });
21835    cx.assert_editor_state(indoc! {"
21836        def main():
21837            if i == 2:
21838                return
21839            else:ˇ
21840    "});
21841
21842    // test `except` auto outdents when typed inside `try` block
21843    cx.set_state(indoc! {"
21844        def main():
21845            try:
21846                i = 2
21847                ˇ
21848    "});
21849    cx.update_editor(|editor, window, cx| {
21850        editor.handle_input("except:", window, cx);
21851    });
21852    cx.assert_editor_state(indoc! {"
21853        def main():
21854            try:
21855                i = 2
21856            except:ˇ
21857    "});
21858
21859    // test `else` auto outdents when typed inside `except` block
21860    cx.set_state(indoc! {"
21861        def main():
21862            try:
21863                i = 2
21864            except:
21865                j = 2
21866                ˇ
21867    "});
21868    cx.update_editor(|editor, window, cx| {
21869        editor.handle_input("else:", window, cx);
21870    });
21871    cx.assert_editor_state(indoc! {"
21872        def main():
21873            try:
21874                i = 2
21875            except:
21876                j = 2
21877            else:ˇ
21878    "});
21879
21880    // test `finally` auto outdents when typed inside `else` block
21881    cx.set_state(indoc! {"
21882        def main():
21883            try:
21884                i = 2
21885            except:
21886                j = 2
21887            else:
21888                k = 2
21889                ˇ
21890    "});
21891    cx.update_editor(|editor, window, cx| {
21892        editor.handle_input("finally:", window, cx);
21893    });
21894    cx.assert_editor_state(indoc! {"
21895        def main():
21896            try:
21897                i = 2
21898            except:
21899                j = 2
21900            else:
21901                k = 2
21902            finally:ˇ
21903    "});
21904
21905    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21906    // cx.set_state(indoc! {"
21907    //     def main():
21908    //         try:
21909    //             for i in range(n):
21910    //                 pass
21911    //             ˇ
21912    // "});
21913    // cx.update_editor(|editor, window, cx| {
21914    //     editor.handle_input("except:", window, cx);
21915    // });
21916    // cx.assert_editor_state(indoc! {"
21917    //     def main():
21918    //         try:
21919    //             for i in range(n):
21920    //                 pass
21921    //         except:ˇ
21922    // "});
21923
21924    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21925    // cx.set_state(indoc! {"
21926    //     def main():
21927    //         try:
21928    //             i = 2
21929    //         except:
21930    //             for i in range(n):
21931    //                 pass
21932    //             ˇ
21933    // "});
21934    // cx.update_editor(|editor, window, cx| {
21935    //     editor.handle_input("else:", window, cx);
21936    // });
21937    // cx.assert_editor_state(indoc! {"
21938    //     def main():
21939    //         try:
21940    //             i = 2
21941    //         except:
21942    //             for i in range(n):
21943    //                 pass
21944    //         else:ˇ
21945    // "});
21946
21947    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21948    // cx.set_state(indoc! {"
21949    //     def main():
21950    //         try:
21951    //             i = 2
21952    //         except:
21953    //             j = 2
21954    //         else:
21955    //             for i in range(n):
21956    //                 pass
21957    //             ˇ
21958    // "});
21959    // cx.update_editor(|editor, window, cx| {
21960    //     editor.handle_input("finally:", window, cx);
21961    // });
21962    // cx.assert_editor_state(indoc! {"
21963    //     def main():
21964    //         try:
21965    //             i = 2
21966    //         except:
21967    //             j = 2
21968    //         else:
21969    //             for i in range(n):
21970    //                 pass
21971    //         finally:ˇ
21972    // "});
21973
21974    // test `else` stays at correct indent when typed after `for` block
21975    cx.set_state(indoc! {"
21976        def main():
21977            for i in range(10):
21978                if i == 3:
21979                    break
21980            ˇ
21981    "});
21982    cx.update_editor(|editor, window, cx| {
21983        editor.handle_input("else:", window, cx);
21984    });
21985    cx.assert_editor_state(indoc! {"
21986        def main():
21987            for i in range(10):
21988                if i == 3:
21989                    break
21990            else:ˇ
21991    "});
21992
21993    // test does not outdent on typing after line with square brackets
21994    cx.set_state(indoc! {"
21995        def f() -> list[str]:
21996            ˇ
21997    "});
21998    cx.update_editor(|editor, window, cx| {
21999        editor.handle_input("a", window, cx);
22000    });
22001    cx.assert_editor_state(indoc! {"
22002        def f() -> list[str]:
2200322004    "});
22005}
22006
22007#[gpui::test]
22008async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22009    init_test(cx, |_| {});
22010    update_test_language_settings(cx, |settings| {
22011        settings.defaults.extend_comment_on_newline = Some(false);
22012    });
22013    let mut cx = EditorTestContext::new(cx).await;
22014    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22015    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22016
22017    // test correct indent after newline on comment
22018    cx.set_state(indoc! {"
22019        # COMMENT:ˇ
22020    "});
22021    cx.update_editor(|editor, window, cx| {
22022        editor.newline(&Newline, window, cx);
22023    });
22024    cx.assert_editor_state(indoc! {"
22025        # COMMENT:
22026        ˇ
22027    "});
22028
22029    // test correct indent after newline in brackets
22030    cx.set_state(indoc! {"
22031        {ˇ}
22032    "});
22033    cx.update_editor(|editor, window, cx| {
22034        editor.newline(&Newline, window, cx);
22035    });
22036    cx.run_until_parked();
22037    cx.assert_editor_state(indoc! {"
22038        {
22039            ˇ
22040        }
22041    "});
22042
22043    cx.set_state(indoc! {"
22044        (ˇ)
22045    "});
22046    cx.update_editor(|editor, window, cx| {
22047        editor.newline(&Newline, window, cx);
22048    });
22049    cx.run_until_parked();
22050    cx.assert_editor_state(indoc! {"
22051        (
22052            ˇ
22053        )
22054    "});
22055
22056    // do not indent after empty lists or dictionaries
22057    cx.set_state(indoc! {"
22058        a = []ˇ
22059    "});
22060    cx.update_editor(|editor, window, cx| {
22061        editor.newline(&Newline, window, cx);
22062    });
22063    cx.run_until_parked();
22064    cx.assert_editor_state(indoc! {"
22065        a = []
22066        ˇ
22067    "});
22068}
22069
22070fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22071    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22072    point..point
22073}
22074
22075#[track_caller]
22076fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22077    let (text, ranges) = marked_text_ranges(marked_text, true);
22078    assert_eq!(editor.text(cx), text);
22079    assert_eq!(
22080        editor.selections.ranges(cx),
22081        ranges,
22082        "Assert selections are {}",
22083        marked_text
22084    );
22085}
22086
22087pub fn handle_signature_help_request(
22088    cx: &mut EditorLspTestContext,
22089    mocked_response: lsp::SignatureHelp,
22090) -> impl Future<Output = ()> + use<> {
22091    let mut request =
22092        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22093            let mocked_response = mocked_response.clone();
22094            async move { Ok(Some(mocked_response)) }
22095        });
22096
22097    async move {
22098        request.next().await;
22099    }
22100}
22101
22102#[track_caller]
22103pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22104    cx.update_editor(|editor, _, _| {
22105        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22106            let entries = menu.entries.borrow();
22107            let entries = entries
22108                .iter()
22109                .map(|entry| entry.string.as_str())
22110                .collect::<Vec<_>>();
22111            assert_eq!(entries, expected);
22112        } else {
22113            panic!("Expected completions menu");
22114        }
22115    });
22116}
22117
22118/// Handle completion request passing a marked string specifying where the completion
22119/// should be triggered from using '|' character, what range should be replaced, and what completions
22120/// should be returned using '<' and '>' to delimit the range.
22121///
22122/// Also see `handle_completion_request_with_insert_and_replace`.
22123#[track_caller]
22124pub fn handle_completion_request(
22125    marked_string: &str,
22126    completions: Vec<&'static str>,
22127    is_incomplete: bool,
22128    counter: Arc<AtomicUsize>,
22129    cx: &mut EditorLspTestContext,
22130) -> impl Future<Output = ()> {
22131    let complete_from_marker: TextRangeMarker = '|'.into();
22132    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22133    let (_, mut marked_ranges) = marked_text_ranges_by(
22134        marked_string,
22135        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22136    );
22137
22138    let complete_from_position =
22139        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22140    let replace_range =
22141        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22142
22143    let mut request =
22144        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22145            let completions = completions.clone();
22146            counter.fetch_add(1, atomic::Ordering::Release);
22147            async move {
22148                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22149                assert_eq!(
22150                    params.text_document_position.position,
22151                    complete_from_position
22152                );
22153                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22154                    is_incomplete: is_incomplete,
22155                    item_defaults: None,
22156                    items: completions
22157                        .iter()
22158                        .map(|completion_text| lsp::CompletionItem {
22159                            label: completion_text.to_string(),
22160                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22161                                range: replace_range,
22162                                new_text: completion_text.to_string(),
22163                            })),
22164                            ..Default::default()
22165                        })
22166                        .collect(),
22167                })))
22168            }
22169        });
22170
22171    async move {
22172        request.next().await;
22173    }
22174}
22175
22176/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22177/// given instead, which also contains an `insert` range.
22178///
22179/// This function uses markers to define ranges:
22180/// - `|` marks the cursor position
22181/// - `<>` marks the replace range
22182/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22183pub fn handle_completion_request_with_insert_and_replace(
22184    cx: &mut EditorLspTestContext,
22185    marked_string: &str,
22186    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22187    counter: Arc<AtomicUsize>,
22188) -> impl Future<Output = ()> {
22189    let complete_from_marker: TextRangeMarker = '|'.into();
22190    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22191    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22192
22193    let (_, mut marked_ranges) = marked_text_ranges_by(
22194        marked_string,
22195        vec![
22196            complete_from_marker.clone(),
22197            replace_range_marker.clone(),
22198            insert_range_marker.clone(),
22199        ],
22200    );
22201
22202    let complete_from_position =
22203        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22204    let replace_range =
22205        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22206
22207    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22208        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22209        _ => lsp::Range {
22210            start: replace_range.start,
22211            end: complete_from_position,
22212        },
22213    };
22214
22215    let mut request =
22216        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22217            let completions = completions.clone();
22218            counter.fetch_add(1, atomic::Ordering::Release);
22219            async move {
22220                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22221                assert_eq!(
22222                    params.text_document_position.position, complete_from_position,
22223                    "marker `|` position doesn't match",
22224                );
22225                Ok(Some(lsp::CompletionResponse::Array(
22226                    completions
22227                        .iter()
22228                        .map(|(label, new_text)| lsp::CompletionItem {
22229                            label: label.to_string(),
22230                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22231                                lsp::InsertReplaceEdit {
22232                                    insert: insert_range,
22233                                    replace: replace_range,
22234                                    new_text: new_text.to_string(),
22235                                },
22236                            )),
22237                            ..Default::default()
22238                        })
22239                        .collect(),
22240                )))
22241            }
22242        });
22243
22244    async move {
22245        request.next().await;
22246    }
22247}
22248
22249fn handle_resolve_completion_request(
22250    cx: &mut EditorLspTestContext,
22251    edits: Option<Vec<(&'static str, &'static str)>>,
22252) -> impl Future<Output = ()> {
22253    let edits = edits.map(|edits| {
22254        edits
22255            .iter()
22256            .map(|(marked_string, new_text)| {
22257                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22258                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22259                lsp::TextEdit::new(replace_range, new_text.to_string())
22260            })
22261            .collect::<Vec<_>>()
22262    });
22263
22264    let mut request =
22265        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22266            let edits = edits.clone();
22267            async move {
22268                Ok(lsp::CompletionItem {
22269                    additional_text_edits: edits,
22270                    ..Default::default()
22271                })
22272            }
22273        });
22274
22275    async move {
22276        request.next().await;
22277    }
22278}
22279
22280pub(crate) fn update_test_language_settings(
22281    cx: &mut TestAppContext,
22282    f: impl Fn(&mut AllLanguageSettingsContent),
22283) {
22284    cx.update(|cx| {
22285        SettingsStore::update_global(cx, |store, cx| {
22286            store.update_user_settings::<AllLanguageSettings>(cx, f);
22287        });
22288    });
22289}
22290
22291pub(crate) fn update_test_project_settings(
22292    cx: &mut TestAppContext,
22293    f: impl Fn(&mut ProjectSettings),
22294) {
22295    cx.update(|cx| {
22296        SettingsStore::update_global(cx, |store, cx| {
22297            store.update_user_settings::<ProjectSettings>(cx, f);
22298        });
22299    });
22300}
22301
22302pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22303    cx.update(|cx| {
22304        assets::Assets.load_test_fonts(cx);
22305        let store = SettingsStore::test(cx);
22306        cx.set_global(store);
22307        theme::init(theme::LoadThemes::JustBase, cx);
22308        release_channel::init(SemanticVersion::default(), cx);
22309        client::init_settings(cx);
22310        language::init(cx);
22311        Project::init_settings(cx);
22312        workspace::init_settings(cx);
22313        crate::init(cx);
22314    });
22315
22316    update_test_language_settings(cx, f);
22317}
22318
22319#[track_caller]
22320fn assert_hunk_revert(
22321    not_reverted_text_with_selections: &str,
22322    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22323    expected_reverted_text_with_selections: &str,
22324    base_text: &str,
22325    cx: &mut EditorLspTestContext,
22326) {
22327    cx.set_state(not_reverted_text_with_selections);
22328    cx.set_head_text(base_text);
22329    cx.executor().run_until_parked();
22330
22331    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22332        let snapshot = editor.snapshot(window, cx);
22333        let reverted_hunk_statuses = snapshot
22334            .buffer_snapshot
22335            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22336            .map(|hunk| hunk.status().kind)
22337            .collect::<Vec<_>>();
22338
22339        editor.git_restore(&Default::default(), window, cx);
22340        reverted_hunk_statuses
22341    });
22342    cx.executor().run_until_parked();
22343    cx.assert_editor_state(expected_reverted_text_with_selections);
22344    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22345}
22346
22347#[gpui::test(iterations = 10)]
22348async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22349    init_test(cx, |_| {});
22350
22351    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22352    let counter = diagnostic_requests.clone();
22353
22354    let fs = FakeFs::new(cx.executor());
22355    fs.insert_tree(
22356        path!("/a"),
22357        json!({
22358            "first.rs": "fn main() { let a = 5; }",
22359            "second.rs": "// Test file",
22360        }),
22361    )
22362    .await;
22363
22364    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22365    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22366    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22367
22368    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22369    language_registry.add(rust_lang());
22370    let mut fake_servers = language_registry.register_fake_lsp(
22371        "Rust",
22372        FakeLspAdapter {
22373            capabilities: lsp::ServerCapabilities {
22374                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22375                    lsp::DiagnosticOptions {
22376                        identifier: None,
22377                        inter_file_dependencies: true,
22378                        workspace_diagnostics: true,
22379                        work_done_progress_options: Default::default(),
22380                    },
22381                )),
22382                ..Default::default()
22383            },
22384            ..Default::default()
22385        },
22386    );
22387
22388    let editor = workspace
22389        .update(cx, |workspace, window, cx| {
22390            workspace.open_abs_path(
22391                PathBuf::from(path!("/a/first.rs")),
22392                OpenOptions::default(),
22393                window,
22394                cx,
22395            )
22396        })
22397        .unwrap()
22398        .await
22399        .unwrap()
22400        .downcast::<Editor>()
22401        .unwrap();
22402    let fake_server = fake_servers.next().await.unwrap();
22403    let server_id = fake_server.server.server_id();
22404    let mut first_request = fake_server
22405        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22406            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22407            let result_id = Some(new_result_id.to_string());
22408            assert_eq!(
22409                params.text_document.uri,
22410                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22411            );
22412            async move {
22413                Ok(lsp::DocumentDiagnosticReportResult::Report(
22414                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22415                        related_documents: None,
22416                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22417                            items: Vec::new(),
22418                            result_id,
22419                        },
22420                    }),
22421                ))
22422            }
22423        });
22424
22425    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22426        project.update(cx, |project, cx| {
22427            let buffer_id = editor
22428                .read(cx)
22429                .buffer()
22430                .read(cx)
22431                .as_singleton()
22432                .expect("created a singleton buffer")
22433                .read(cx)
22434                .remote_id();
22435            let buffer_result_id = project
22436                .lsp_store()
22437                .read(cx)
22438                .result_id(server_id, buffer_id, cx);
22439            assert_eq!(expected, buffer_result_id);
22440        });
22441    };
22442
22443    ensure_result_id(None, cx);
22444    cx.executor().advance_clock(Duration::from_millis(60));
22445    cx.executor().run_until_parked();
22446    assert_eq!(
22447        diagnostic_requests.load(atomic::Ordering::Acquire),
22448        1,
22449        "Opening file should trigger diagnostic request"
22450    );
22451    first_request
22452        .next()
22453        .await
22454        .expect("should have sent the first diagnostics pull request");
22455    ensure_result_id(Some("1".to_string()), cx);
22456
22457    // Editing should trigger diagnostics
22458    editor.update_in(cx, |editor, window, cx| {
22459        editor.handle_input("2", window, cx)
22460    });
22461    cx.executor().advance_clock(Duration::from_millis(60));
22462    cx.executor().run_until_parked();
22463    assert_eq!(
22464        diagnostic_requests.load(atomic::Ordering::Acquire),
22465        2,
22466        "Editing should trigger diagnostic request"
22467    );
22468    ensure_result_id(Some("2".to_string()), cx);
22469
22470    // Moving cursor should not trigger diagnostic request
22471    editor.update_in(cx, |editor, window, cx| {
22472        editor.change_selections(None, window, cx, |s| {
22473            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22474        });
22475    });
22476    cx.executor().advance_clock(Duration::from_millis(60));
22477    cx.executor().run_until_parked();
22478    assert_eq!(
22479        diagnostic_requests.load(atomic::Ordering::Acquire),
22480        2,
22481        "Cursor movement should not trigger diagnostic request"
22482    );
22483    ensure_result_id(Some("2".to_string()), cx);
22484    // Multiple rapid edits should be debounced
22485    for _ in 0..5 {
22486        editor.update_in(cx, |editor, window, cx| {
22487            editor.handle_input("x", window, cx)
22488        });
22489    }
22490    cx.executor().advance_clock(Duration::from_millis(60));
22491    cx.executor().run_until_parked();
22492
22493    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22494    assert!(
22495        final_requests <= 4,
22496        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22497    );
22498    ensure_result_id(Some(final_requests.to_string()), cx);
22499}
22500
22501#[gpui::test]
22502async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22503    // Regression test for issue #11671
22504    // Previously, adding a cursor after moving multiple cursors would reset
22505    // the cursor count instead of adding to the existing cursors.
22506    init_test(cx, |_| {});
22507    let mut cx = EditorTestContext::new(cx).await;
22508
22509    // Create a simple buffer with cursor at start
22510    cx.set_state(indoc! {"
22511        ˇaaaa
22512        bbbb
22513        cccc
22514        dddd
22515        eeee
22516        ffff
22517        gggg
22518        hhhh"});
22519
22520    // Add 2 cursors below (so we have 3 total)
22521    cx.update_editor(|editor, window, cx| {
22522        editor.add_selection_below(&Default::default(), window, cx);
22523        editor.add_selection_below(&Default::default(), window, cx);
22524    });
22525
22526    // Verify we have 3 cursors
22527    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22528    assert_eq!(
22529        initial_count, 3,
22530        "Should have 3 cursors after adding 2 below"
22531    );
22532
22533    // Move down one line
22534    cx.update_editor(|editor, window, cx| {
22535        editor.move_down(&MoveDown, window, cx);
22536    });
22537
22538    // Add another cursor below
22539    cx.update_editor(|editor, window, cx| {
22540        editor.add_selection_below(&Default::default(), window, cx);
22541    });
22542
22543    // Should now have 4 cursors (3 original + 1 new)
22544    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22545    assert_eq!(
22546        final_count, 4,
22547        "Should have 4 cursors after moving and adding another"
22548    );
22549}
22550
22551#[gpui::test(iterations = 10)]
22552async fn test_document_colors(cx: &mut TestAppContext) {
22553    let expected_color = Rgba {
22554        r: 0.33,
22555        g: 0.33,
22556        b: 0.33,
22557        a: 0.33,
22558    };
22559
22560    init_test(cx, |_| {});
22561
22562    let fs = FakeFs::new(cx.executor());
22563    fs.insert_tree(
22564        path!("/a"),
22565        json!({
22566            "first.rs": "fn main() { let a = 5; }",
22567        }),
22568    )
22569    .await;
22570
22571    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22572    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22573    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22574
22575    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22576    language_registry.add(rust_lang());
22577    let mut fake_servers = language_registry.register_fake_lsp(
22578        "Rust",
22579        FakeLspAdapter {
22580            capabilities: lsp::ServerCapabilities {
22581                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22582                ..lsp::ServerCapabilities::default()
22583            },
22584            name: "rust-analyzer",
22585            ..FakeLspAdapter::default()
22586        },
22587    );
22588    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22589        "Rust",
22590        FakeLspAdapter {
22591            capabilities: lsp::ServerCapabilities {
22592                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22593                ..lsp::ServerCapabilities::default()
22594            },
22595            name: "not-rust-analyzer",
22596            ..FakeLspAdapter::default()
22597        },
22598    );
22599
22600    let editor = workspace
22601        .update(cx, |workspace, window, cx| {
22602            workspace.open_abs_path(
22603                PathBuf::from(path!("/a/first.rs")),
22604                OpenOptions::default(),
22605                window,
22606                cx,
22607            )
22608        })
22609        .unwrap()
22610        .await
22611        .unwrap()
22612        .downcast::<Editor>()
22613        .unwrap();
22614    let fake_language_server = fake_servers.next().await.unwrap();
22615    let fake_language_server_without_capabilities =
22616        fake_servers_without_capabilities.next().await.unwrap();
22617    let requests_made = Arc::new(AtomicUsize::new(0));
22618    let closure_requests_made = Arc::clone(&requests_made);
22619    let mut color_request_handle = fake_language_server
22620        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22621            let requests_made = Arc::clone(&closure_requests_made);
22622            async move {
22623                assert_eq!(
22624                    params.text_document.uri,
22625                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22626                );
22627                requests_made.fetch_add(1, atomic::Ordering::Release);
22628                Ok(vec![
22629                    lsp::ColorInformation {
22630                        range: lsp::Range {
22631                            start: lsp::Position {
22632                                line: 0,
22633                                character: 0,
22634                            },
22635                            end: lsp::Position {
22636                                line: 0,
22637                                character: 1,
22638                            },
22639                        },
22640                        color: lsp::Color {
22641                            red: 0.33,
22642                            green: 0.33,
22643                            blue: 0.33,
22644                            alpha: 0.33,
22645                        },
22646                    },
22647                    lsp::ColorInformation {
22648                        range: lsp::Range {
22649                            start: lsp::Position {
22650                                line: 0,
22651                                character: 0,
22652                            },
22653                            end: lsp::Position {
22654                                line: 0,
22655                                character: 1,
22656                            },
22657                        },
22658                        color: lsp::Color {
22659                            red: 0.33,
22660                            green: 0.33,
22661                            blue: 0.33,
22662                            alpha: 0.33,
22663                        },
22664                    },
22665                ])
22666            }
22667        });
22668
22669    let _handle = fake_language_server_without_capabilities
22670        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22671            panic!("Should not be called");
22672        });
22673    cx.executor().advance_clock(Duration::from_millis(100));
22674    color_request_handle.next().await.unwrap();
22675    cx.run_until_parked();
22676    assert_eq!(
22677        1,
22678        requests_made.load(atomic::Ordering::Acquire),
22679        "Should query for colors once per editor open"
22680    );
22681    editor.update_in(cx, |editor, _, cx| {
22682        assert_eq!(
22683            vec![expected_color],
22684            extract_color_inlays(editor, cx),
22685            "Should have an initial inlay"
22686        );
22687    });
22688
22689    // opening another file in a split should not influence the LSP query counter
22690    workspace
22691        .update(cx, |workspace, window, cx| {
22692            assert_eq!(
22693                workspace.panes().len(),
22694                1,
22695                "Should have one pane with one editor"
22696            );
22697            workspace.move_item_to_pane_in_direction(
22698                &MoveItemToPaneInDirection {
22699                    direction: SplitDirection::Right,
22700                    focus: false,
22701                    clone: true,
22702                },
22703                window,
22704                cx,
22705            );
22706        })
22707        .unwrap();
22708    cx.run_until_parked();
22709    workspace
22710        .update(cx, |workspace, _, cx| {
22711            let panes = workspace.panes();
22712            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
22713            for pane in panes {
22714                let editor = pane
22715                    .read(cx)
22716                    .active_item()
22717                    .and_then(|item| item.downcast::<Editor>())
22718                    .expect("Should have opened an editor in each split");
22719                let editor_file = editor
22720                    .read(cx)
22721                    .buffer()
22722                    .read(cx)
22723                    .as_singleton()
22724                    .expect("test deals with singleton buffers")
22725                    .read(cx)
22726                    .file()
22727                    .expect("test buffese should have a file")
22728                    .path();
22729                assert_eq!(
22730                    editor_file.as_ref(),
22731                    Path::new("first.rs"),
22732                    "Both editors should be opened for the same file"
22733                )
22734            }
22735        })
22736        .unwrap();
22737
22738    cx.executor().advance_clock(Duration::from_millis(500));
22739    let save = editor.update_in(cx, |editor, window, cx| {
22740        editor.move_to_end(&MoveToEnd, window, cx);
22741        editor.handle_input("dirty", window, cx);
22742        editor.save(
22743            SaveOptions {
22744                format: true,
22745                autosave: true,
22746            },
22747            project.clone(),
22748            window,
22749            cx,
22750        )
22751    });
22752    save.await.unwrap();
22753
22754    color_request_handle.next().await.unwrap();
22755    cx.run_until_parked();
22756    assert_eq!(
22757        3,
22758        requests_made.load(atomic::Ordering::Acquire),
22759        "Should query for colors once per save and once per formatting after save"
22760    );
22761
22762    drop(editor);
22763    let close = workspace
22764        .update(cx, |workspace, window, cx| {
22765            workspace.active_pane().update(cx, |pane, cx| {
22766                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22767            })
22768        })
22769        .unwrap();
22770    close.await.unwrap();
22771    let close = workspace
22772        .update(cx, |workspace, window, cx| {
22773            workspace.active_pane().update(cx, |pane, cx| {
22774                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22775            })
22776        })
22777        .unwrap();
22778    close.await.unwrap();
22779    assert_eq!(
22780        3,
22781        requests_made.load(atomic::Ordering::Acquire),
22782        "After saving and closing all editors, no extra requests should be made"
22783    );
22784    workspace
22785        .update(cx, |workspace, _, cx| {
22786            assert!(
22787                workspace.active_item(cx).is_none(),
22788                "Should close all editors"
22789            )
22790        })
22791        .unwrap();
22792
22793    workspace
22794        .update(cx, |workspace, window, cx| {
22795            workspace.active_pane().update(cx, |pane, cx| {
22796                pane.navigate_backward(window, cx);
22797            })
22798        })
22799        .unwrap();
22800    cx.executor().advance_clock(Duration::from_millis(100));
22801    cx.run_until_parked();
22802    let editor = workspace
22803        .update(cx, |workspace, _, cx| {
22804            workspace
22805                .active_item(cx)
22806                .expect("Should have reopened the editor again after navigating back")
22807                .downcast::<Editor>()
22808                .expect("Should be an editor")
22809        })
22810        .unwrap();
22811    color_request_handle.next().await.unwrap();
22812    assert_eq!(
22813        3,
22814        requests_made.load(atomic::Ordering::Acquire),
22815        "Cache should be reused on buffer close and reopen"
22816    );
22817    editor.update(cx, |editor, cx| {
22818        assert_eq!(
22819            vec![expected_color],
22820            extract_color_inlays(editor, cx),
22821            "Should have an initial inlay"
22822        );
22823    });
22824}
22825
22826#[track_caller]
22827fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22828    editor
22829        .all_inlays(cx)
22830        .into_iter()
22831        .filter_map(|inlay| inlay.get_color())
22832        .map(Rgba::from)
22833        .collect()
22834}