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    ActivatePane, CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions,
   59    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_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_lines() with a single driver method: sort_lines_case_sensitive()
 4026    // Since all methods calling manipulate_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_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_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_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_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_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_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_toggle_case(cx: &mut TestAppContext) {
 4315    init_test(cx, |_| {});
 4316
 4317    let mut cx = EditorTestContext::new(cx).await;
 4318
 4319    // If all lower case -> upper case
 4320    cx.set_state(indoc! {"
 4321        «hello worldˇ»
 4322    "});
 4323    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4324    cx.assert_editor_state(indoc! {"
 4325        «HELLO WORLDˇ»
 4326    "});
 4327
 4328    // If all upper case -> lower case
 4329    cx.set_state(indoc! {"
 4330        «HELLO WORLDˇ»
 4331    "});
 4332    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4333    cx.assert_editor_state(indoc! {"
 4334        «hello worldˇ»
 4335    "});
 4336
 4337    // If any upper case characters are identified -> lower case
 4338    // This matches JetBrains IDEs
 4339    cx.set_state(indoc! {"
 4340        «hEllo worldˇ»
 4341    "});
 4342    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4343    cx.assert_editor_state(indoc! {"
 4344        «hello worldˇ»
 4345    "});
 4346}
 4347
 4348#[gpui::test]
 4349async fn test_manipulate_text(cx: &mut TestAppContext) {
 4350    init_test(cx, |_| {});
 4351
 4352    let mut cx = EditorTestContext::new(cx).await;
 4353
 4354    // Test convert_to_upper_case()
 4355    cx.set_state(indoc! {"
 4356        «hello worldˇ»
 4357    "});
 4358    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4359    cx.assert_editor_state(indoc! {"
 4360        «HELLO WORLDˇ»
 4361    "});
 4362
 4363    // Test convert_to_lower_case()
 4364    cx.set_state(indoc! {"
 4365        «HELLO WORLDˇ»
 4366    "});
 4367    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4368    cx.assert_editor_state(indoc! {"
 4369        «hello worldˇ»
 4370    "});
 4371
 4372    // Test multiple line, single selection case
 4373    cx.set_state(indoc! {"
 4374        «The quick brown
 4375        fox jumps over
 4376        the lazy dogˇ»
 4377    "});
 4378    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4379    cx.assert_editor_state(indoc! {"
 4380        «The Quick Brown
 4381        Fox Jumps Over
 4382        The Lazy Dogˇ»
 4383    "});
 4384
 4385    // Test multiple line, single selection case
 4386    cx.set_state(indoc! {"
 4387        «The quick brown
 4388        fox jumps over
 4389        the lazy dogˇ»
 4390    "});
 4391    cx.update_editor(|e, window, cx| {
 4392        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4393    });
 4394    cx.assert_editor_state(indoc! {"
 4395        «TheQuickBrown
 4396        FoxJumpsOver
 4397        TheLazyDogˇ»
 4398    "});
 4399
 4400    // From here on out, test more complex cases of manipulate_text()
 4401
 4402    // Test no selection case - should affect words cursors are in
 4403    // Cursor at beginning, middle, and end of word
 4404    cx.set_state(indoc! {"
 4405        ˇhello big beauˇtiful worldˇ
 4406    "});
 4407    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4408    cx.assert_editor_state(indoc! {"
 4409        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4410    "});
 4411
 4412    // Test multiple selections on a single line and across multiple lines
 4413    cx.set_state(indoc! {"
 4414        «Theˇ» quick «brown
 4415        foxˇ» jumps «overˇ»
 4416        the «lazyˇ» dog
 4417    "});
 4418    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4419    cx.assert_editor_state(indoc! {"
 4420        «THEˇ» quick «BROWN
 4421        FOXˇ» jumps «OVERˇ»
 4422        the «LAZYˇ» dog
 4423    "});
 4424
 4425    // Test case where text length grows
 4426    cx.set_state(indoc! {"
 4427        «tschüߡ»
 4428    "});
 4429    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4430    cx.assert_editor_state(indoc! {"
 4431        «TSCHÜSSˇ»
 4432    "});
 4433
 4434    // Test to make sure we don't crash when text shrinks
 4435    cx.set_state(indoc! {"
 4436        aaa_bbbˇ
 4437    "});
 4438    cx.update_editor(|e, window, cx| {
 4439        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4440    });
 4441    cx.assert_editor_state(indoc! {"
 4442        «aaaBbbˇ»
 4443    "});
 4444
 4445    // Test to make sure we all aware of the fact that each word can grow and shrink
 4446    // Final selections should be aware of this fact
 4447    cx.set_state(indoc! {"
 4448        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4449    "});
 4450    cx.update_editor(|e, window, cx| {
 4451        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4452    });
 4453    cx.assert_editor_state(indoc! {"
 4454        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4455    "});
 4456
 4457    cx.set_state(indoc! {"
 4458        «hElLo, WoRld!ˇ»
 4459    "});
 4460    cx.update_editor(|e, window, cx| {
 4461        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4462    });
 4463    cx.assert_editor_state(indoc! {"
 4464        «HeLlO, wOrLD!ˇ»
 4465    "});
 4466}
 4467
 4468#[gpui::test]
 4469fn test_duplicate_line(cx: &mut TestAppContext) {
 4470    init_test(cx, |_| {});
 4471
 4472    let editor = cx.add_window(|window, cx| {
 4473        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4474        build_editor(buffer, window, cx)
 4475    });
 4476    _ = editor.update(cx, |editor, window, cx| {
 4477        editor.change_selections(None, window, cx, |s| {
 4478            s.select_display_ranges([
 4479                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4480                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4481                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4482                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4483            ])
 4484        });
 4485        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4486        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4487        assert_eq!(
 4488            editor.selections.display_ranges(cx),
 4489            vec![
 4490                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4491                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4492                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4493                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4494            ]
 4495        );
 4496    });
 4497
 4498    let editor = cx.add_window(|window, cx| {
 4499        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4500        build_editor(buffer, window, cx)
 4501    });
 4502    _ = editor.update(cx, |editor, window, cx| {
 4503        editor.change_selections(None, window, cx, |s| {
 4504            s.select_display_ranges([
 4505                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4506                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4507            ])
 4508        });
 4509        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4510        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4511        assert_eq!(
 4512            editor.selections.display_ranges(cx),
 4513            vec![
 4514                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4515                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4516            ]
 4517        );
 4518    });
 4519
 4520    // With `move_upwards` the selections stay in place, except for
 4521    // the lines inserted above them
 4522    let editor = cx.add_window(|window, cx| {
 4523        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4524        build_editor(buffer, window, cx)
 4525    });
 4526    _ = editor.update(cx, |editor, window, cx| {
 4527        editor.change_selections(None, window, cx, |s| {
 4528            s.select_display_ranges([
 4529                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4530                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4531                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4532                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4533            ])
 4534        });
 4535        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4536        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4537        assert_eq!(
 4538            editor.selections.display_ranges(cx),
 4539            vec![
 4540                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4541                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4542                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4543                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4544            ]
 4545        );
 4546    });
 4547
 4548    let editor = cx.add_window(|window, cx| {
 4549        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4550        build_editor(buffer, window, cx)
 4551    });
 4552    _ = editor.update(cx, |editor, window, cx| {
 4553        editor.change_selections(None, window, cx, |s| {
 4554            s.select_display_ranges([
 4555                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4556                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4557            ])
 4558        });
 4559        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4560        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4561        assert_eq!(
 4562            editor.selections.display_ranges(cx),
 4563            vec![
 4564                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4565                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4566            ]
 4567        );
 4568    });
 4569
 4570    let editor = cx.add_window(|window, cx| {
 4571        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4572        build_editor(buffer, window, cx)
 4573    });
 4574    _ = editor.update(cx, |editor, window, cx| {
 4575        editor.change_selections(None, window, cx, |s| {
 4576            s.select_display_ranges([
 4577                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4578                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4579            ])
 4580        });
 4581        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4582        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4583        assert_eq!(
 4584            editor.selections.display_ranges(cx),
 4585            vec![
 4586                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4587                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4588            ]
 4589        );
 4590    });
 4591}
 4592
 4593#[gpui::test]
 4594fn test_move_line_up_down(cx: &mut TestAppContext) {
 4595    init_test(cx, |_| {});
 4596
 4597    let editor = cx.add_window(|window, cx| {
 4598        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4599        build_editor(buffer, window, cx)
 4600    });
 4601    _ = editor.update(cx, |editor, window, cx| {
 4602        editor.fold_creases(
 4603            vec![
 4604                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4605                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4606                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4607            ],
 4608            true,
 4609            window,
 4610            cx,
 4611        );
 4612        editor.change_selections(None, window, cx, |s| {
 4613            s.select_display_ranges([
 4614                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4615                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4616                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4617                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4618            ])
 4619        });
 4620        assert_eq!(
 4621            editor.display_text(cx),
 4622            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4623        );
 4624
 4625        editor.move_line_up(&MoveLineUp, window, cx);
 4626        assert_eq!(
 4627            editor.display_text(cx),
 4628            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4629        );
 4630        assert_eq!(
 4631            editor.selections.display_ranges(cx),
 4632            vec![
 4633                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4634                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4635                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4636                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4637            ]
 4638        );
 4639    });
 4640
 4641    _ = editor.update(cx, |editor, window, cx| {
 4642        editor.move_line_down(&MoveLineDown, window, cx);
 4643        assert_eq!(
 4644            editor.display_text(cx),
 4645            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4646        );
 4647        assert_eq!(
 4648            editor.selections.display_ranges(cx),
 4649            vec![
 4650                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4651                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4652                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4653                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4654            ]
 4655        );
 4656    });
 4657
 4658    _ = editor.update(cx, |editor, window, cx| {
 4659        editor.move_line_down(&MoveLineDown, window, cx);
 4660        assert_eq!(
 4661            editor.display_text(cx),
 4662            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4663        );
 4664        assert_eq!(
 4665            editor.selections.display_ranges(cx),
 4666            vec![
 4667                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4668                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4669                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4670                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4671            ]
 4672        );
 4673    });
 4674
 4675    _ = editor.update(cx, |editor, window, cx| {
 4676        editor.move_line_up(&MoveLineUp, window, cx);
 4677        assert_eq!(
 4678            editor.display_text(cx),
 4679            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4680        );
 4681        assert_eq!(
 4682            editor.selections.display_ranges(cx),
 4683            vec![
 4684                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4685                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4686                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4687                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4688            ]
 4689        );
 4690    });
 4691}
 4692
 4693#[gpui::test]
 4694fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4695    init_test(cx, |_| {});
 4696
 4697    let editor = cx.add_window(|window, cx| {
 4698        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4699        build_editor(buffer, window, cx)
 4700    });
 4701    _ = editor.update(cx, |editor, window, cx| {
 4702        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4703        editor.insert_blocks(
 4704            [BlockProperties {
 4705                style: BlockStyle::Fixed,
 4706                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4707                height: Some(1),
 4708                render: Arc::new(|_| div().into_any()),
 4709                priority: 0,
 4710                render_in_minimap: true,
 4711            }],
 4712            Some(Autoscroll::fit()),
 4713            cx,
 4714        );
 4715        editor.change_selections(None, window, cx, |s| {
 4716            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4717        });
 4718        editor.move_line_down(&MoveLineDown, window, cx);
 4719    });
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4724    init_test(cx, |_| {});
 4725
 4726    let mut cx = EditorTestContext::new(cx).await;
 4727    cx.set_state(
 4728        &"
 4729            ˇzero
 4730            one
 4731            two
 4732            three
 4733            four
 4734            five
 4735        "
 4736        .unindent(),
 4737    );
 4738
 4739    // Create a four-line block that replaces three lines of text.
 4740    cx.update_editor(|editor, window, cx| {
 4741        let snapshot = editor.snapshot(window, cx);
 4742        let snapshot = &snapshot.buffer_snapshot;
 4743        let placement = BlockPlacement::Replace(
 4744            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4745        );
 4746        editor.insert_blocks(
 4747            [BlockProperties {
 4748                placement,
 4749                height: Some(4),
 4750                style: BlockStyle::Sticky,
 4751                render: Arc::new(|_| gpui::div().into_any_element()),
 4752                priority: 0,
 4753                render_in_minimap: true,
 4754            }],
 4755            None,
 4756            cx,
 4757        );
 4758    });
 4759
 4760    // Move down so that the cursor touches the block.
 4761    cx.update_editor(|editor, window, cx| {
 4762        editor.move_down(&Default::default(), window, cx);
 4763    });
 4764    cx.assert_editor_state(
 4765        &"
 4766            zero
 4767            «one
 4768            two
 4769            threeˇ»
 4770            four
 4771            five
 4772        "
 4773        .unindent(),
 4774    );
 4775
 4776    // Move down past the block.
 4777    cx.update_editor(|editor, window, cx| {
 4778        editor.move_down(&Default::default(), window, cx);
 4779    });
 4780    cx.assert_editor_state(
 4781        &"
 4782            zero
 4783            one
 4784            two
 4785            three
 4786            ˇfour
 4787            five
 4788        "
 4789        .unindent(),
 4790    );
 4791}
 4792
 4793#[gpui::test]
 4794fn test_transpose(cx: &mut TestAppContext) {
 4795    init_test(cx, |_| {});
 4796
 4797    _ = cx.add_window(|window, cx| {
 4798        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4799        editor.set_style(EditorStyle::default(), window, cx);
 4800        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4801        editor.transpose(&Default::default(), window, cx);
 4802        assert_eq!(editor.text(cx), "bac");
 4803        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4804
 4805        editor.transpose(&Default::default(), window, cx);
 4806        assert_eq!(editor.text(cx), "bca");
 4807        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4808
 4809        editor.transpose(&Default::default(), window, cx);
 4810        assert_eq!(editor.text(cx), "bac");
 4811        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4812
 4813        editor
 4814    });
 4815
 4816    _ = cx.add_window(|window, cx| {
 4817        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4818        editor.set_style(EditorStyle::default(), window, cx);
 4819        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4820        editor.transpose(&Default::default(), window, cx);
 4821        assert_eq!(editor.text(cx), "acb\nde");
 4822        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4823
 4824        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4825        editor.transpose(&Default::default(), window, cx);
 4826        assert_eq!(editor.text(cx), "acbd\ne");
 4827        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4828
 4829        editor.transpose(&Default::default(), window, cx);
 4830        assert_eq!(editor.text(cx), "acbde\n");
 4831        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4832
 4833        editor.transpose(&Default::default(), window, cx);
 4834        assert_eq!(editor.text(cx), "acbd\ne");
 4835        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4836
 4837        editor
 4838    });
 4839
 4840    _ = cx.add_window(|window, cx| {
 4841        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4842        editor.set_style(EditorStyle::default(), window, cx);
 4843        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4844        editor.transpose(&Default::default(), window, cx);
 4845        assert_eq!(editor.text(cx), "bacd\ne");
 4846        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4847
 4848        editor.transpose(&Default::default(), window, cx);
 4849        assert_eq!(editor.text(cx), "bcade\n");
 4850        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4851
 4852        editor.transpose(&Default::default(), window, cx);
 4853        assert_eq!(editor.text(cx), "bcda\ne");
 4854        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4855
 4856        editor.transpose(&Default::default(), window, cx);
 4857        assert_eq!(editor.text(cx), "bcade\n");
 4858        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4859
 4860        editor.transpose(&Default::default(), window, cx);
 4861        assert_eq!(editor.text(cx), "bcaed\n");
 4862        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4863
 4864        editor
 4865    });
 4866
 4867    _ = cx.add_window(|window, cx| {
 4868        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4869        editor.set_style(EditorStyle::default(), window, cx);
 4870        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4871        editor.transpose(&Default::default(), window, cx);
 4872        assert_eq!(editor.text(cx), "🏀🍐✋");
 4873        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4874
 4875        editor.transpose(&Default::default(), window, cx);
 4876        assert_eq!(editor.text(cx), "🏀✋🍐");
 4877        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4878
 4879        editor.transpose(&Default::default(), window, cx);
 4880        assert_eq!(editor.text(cx), "🏀🍐✋");
 4881        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4882
 4883        editor
 4884    });
 4885}
 4886
 4887#[gpui::test]
 4888async fn test_rewrap(cx: &mut TestAppContext) {
 4889    init_test(cx, |settings| {
 4890        settings.languages.extend([
 4891            (
 4892                "Markdown".into(),
 4893                LanguageSettingsContent {
 4894                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4895                    ..Default::default()
 4896                },
 4897            ),
 4898            (
 4899                "Plain Text".into(),
 4900                LanguageSettingsContent {
 4901                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4902                    ..Default::default()
 4903                },
 4904            ),
 4905        ])
 4906    });
 4907
 4908    let mut cx = EditorTestContext::new(cx).await;
 4909
 4910    let language_with_c_comments = Arc::new(Language::new(
 4911        LanguageConfig {
 4912            line_comments: vec!["// ".into()],
 4913            ..LanguageConfig::default()
 4914        },
 4915        None,
 4916    ));
 4917    let language_with_pound_comments = Arc::new(Language::new(
 4918        LanguageConfig {
 4919            line_comments: vec!["# ".into()],
 4920            ..LanguageConfig::default()
 4921        },
 4922        None,
 4923    ));
 4924    let markdown_language = Arc::new(Language::new(
 4925        LanguageConfig {
 4926            name: "Markdown".into(),
 4927            ..LanguageConfig::default()
 4928        },
 4929        None,
 4930    ));
 4931    let language_with_doc_comments = Arc::new(Language::new(
 4932        LanguageConfig {
 4933            line_comments: vec!["// ".into(), "/// ".into()],
 4934            ..LanguageConfig::default()
 4935        },
 4936        Some(tree_sitter_rust::LANGUAGE.into()),
 4937    ));
 4938
 4939    let plaintext_language = Arc::new(Language::new(
 4940        LanguageConfig {
 4941            name: "Plain Text".into(),
 4942            ..LanguageConfig::default()
 4943        },
 4944        None,
 4945    ));
 4946
 4947    assert_rewrap(
 4948        indoc! {"
 4949            // ˇ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.
 4950        "},
 4951        indoc! {"
 4952            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4953            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4954            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4955            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4956            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4957            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4958            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4959            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4960            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4961            // porttitor id. Aliquam id accumsan eros.
 4962        "},
 4963        language_with_c_comments.clone(),
 4964        &mut cx,
 4965    );
 4966
 4967    // Test that rewrapping works inside of a selection
 4968    assert_rewrap(
 4969        indoc! {"
 4970            «// 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.ˇ»
 4971        "},
 4972        indoc! {"
 4973            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4974            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4975            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4976            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4977            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4978            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4979            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4980            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4981            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4982            // porttitor id. Aliquam id accumsan eros.ˇ»
 4983        "},
 4984        language_with_c_comments.clone(),
 4985        &mut cx,
 4986    );
 4987
 4988    // Test that cursors that expand to the same region are collapsed.
 4989    assert_rewrap(
 4990        indoc! {"
 4991            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4992            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4993            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4994            // ˇ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.
 4995        "},
 4996        indoc! {"
 4997            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4998            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4999            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 5000            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 5001            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 5002            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 5003            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 5004            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 5005            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 5006            // porttitor id. Aliquam id accumsan eros.
 5007        "},
 5008        language_with_c_comments.clone(),
 5009        &mut cx,
 5010    );
 5011
 5012    // Test that non-contiguous selections are treated separately.
 5013    assert_rewrap(
 5014        indoc! {"
 5015            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 5016            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 5017            //
 5018            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5019            // ˇ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.
 5020        "},
 5021        indoc! {"
 5022            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 5023            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 5024            // auctor, eu lacinia sapien scelerisque.
 5025            //
 5026            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 5027            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 5028            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 5029            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 5030            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 5031            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 5032            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 5033        "},
 5034        language_with_c_comments.clone(),
 5035        &mut cx,
 5036    );
 5037
 5038    // Test that different comment prefixes are supported.
 5039    assert_rewrap(
 5040        indoc! {"
 5041            # ˇ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.
 5042        "},
 5043        indoc! {"
 5044            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 5045            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5046            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5047            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5048            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 5049            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 5050            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 5051            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 5052            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 5053            # accumsan eros.
 5054        "},
 5055        language_with_pound_comments.clone(),
 5056        &mut cx,
 5057    );
 5058
 5059    // Test that rewrapping is ignored outside of comments in most languages.
 5060    assert_rewrap(
 5061        indoc! {"
 5062            /// Adds two numbers.
 5063            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5064            fn add(a: u32, b: u32) -> u32 {
 5065                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ˇ
 5066            }
 5067        "},
 5068        indoc! {"
 5069            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5070            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 5071            fn add(a: u32, b: u32) -> u32 {
 5072                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ˇ
 5073            }
 5074        "},
 5075        language_with_doc_comments.clone(),
 5076        &mut cx,
 5077    );
 5078
 5079    // Test that rewrapping works in Markdown and Plain Text languages.
 5080    assert_rewrap(
 5081        indoc! {"
 5082            # Hello
 5083
 5084            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.
 5085        "},
 5086        indoc! {"
 5087            # Hello
 5088
 5089            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5090            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5091            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5092            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5093            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5094            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5095            Integer sit amet scelerisque nisi.
 5096        "},
 5097        markdown_language,
 5098        &mut cx,
 5099    );
 5100
 5101    assert_rewrap(
 5102        indoc! {"
 5103            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.
 5104        "},
 5105        indoc! {"
 5106            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 5107            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 5108            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 5109            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 5110            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 5111            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 5112            Integer sit amet scelerisque nisi.
 5113        "},
 5114        plaintext_language.clone(),
 5115        &mut cx,
 5116    );
 5117
 5118    // Test rewrapping unaligned comments in a selection.
 5119    assert_rewrap(
 5120        indoc! {"
 5121            fn foo() {
 5122                if true {
 5123            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5124            // Praesent semper egestas tellus id dignissim.ˇ»
 5125                    do_something();
 5126                } else {
 5127                    //
 5128                }
 5129            }
 5130        "},
 5131        indoc! {"
 5132            fn foo() {
 5133                if true {
 5134            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5135                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5136                    // egestas tellus id dignissim.ˇ»
 5137                    do_something();
 5138                } else {
 5139                    //
 5140                }
 5141            }
 5142        "},
 5143        language_with_doc_comments.clone(),
 5144        &mut cx,
 5145    );
 5146
 5147    assert_rewrap(
 5148        indoc! {"
 5149            fn foo() {
 5150                if true {
 5151            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 5152            // Praesent semper egestas tellus id dignissim.»
 5153                    do_something();
 5154                } else {
 5155                    //
 5156                }
 5157
 5158            }
 5159        "},
 5160        indoc! {"
 5161            fn foo() {
 5162                if true {
 5163            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 5164                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 5165                    // egestas tellus id dignissim.»
 5166                    do_something();
 5167                } else {
 5168                    //
 5169                }
 5170
 5171            }
 5172        "},
 5173        language_with_doc_comments.clone(),
 5174        &mut cx,
 5175    );
 5176
 5177    assert_rewrap(
 5178        indoc! {"
 5179            «ˇ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
 5180
 5181            two»
 5182
 5183            three
 5184
 5185            «ˇ\t
 5186
 5187            four four four four four four four four four four four four four four four four four four four four»
 5188
 5189            «ˇfive five five five five five five five five five five five five five five five five five five five
 5190            \t»
 5191            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
 5192        "},
 5193        indoc! {"
 5194            «ˇone one one one one one one one one one one one one one one one one one one one
 5195            one one one one one
 5196
 5197            two»
 5198
 5199            three
 5200
 5201            «ˇ\t
 5202
 5203            four four four four four four four four four four four four four four four four
 5204            four four four four»
 5205
 5206            «ˇfive five five five five five five five five five five five five five five five
 5207            five five five five
 5208            \t»
 5209            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
 5210        "},
 5211        plaintext_language.clone(),
 5212        &mut cx,
 5213    );
 5214
 5215    assert_rewrap(
 5216        indoc! {"
 5217            //ˇ 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
 5218            //ˇ
 5219            //ˇ 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
 5220            //ˇ short short short
 5221            int main(void) {
 5222                return 17;
 5223            }
 5224        "},
 5225        indoc! {"
 5226            //ˇ long long long long long long long long long long long long long long long
 5227            // long long long long long long long long long long long long long
 5228            //ˇ
 5229            //ˇ long long long long long long long long long long long long long long long
 5230            //ˇ long long long long long long long long long long long long long short short
 5231            // short
 5232            int main(void) {
 5233                return 17;
 5234            }
 5235        "},
 5236        language_with_c_comments,
 5237        &mut cx,
 5238    );
 5239
 5240    #[track_caller]
 5241    fn assert_rewrap(
 5242        unwrapped_text: &str,
 5243        wrapped_text: &str,
 5244        language: Arc<Language>,
 5245        cx: &mut EditorTestContext,
 5246    ) {
 5247        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5248        cx.set_state(unwrapped_text);
 5249        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5250        cx.assert_editor_state(wrapped_text);
 5251    }
 5252}
 5253
 5254#[gpui::test]
 5255async fn test_hard_wrap(cx: &mut TestAppContext) {
 5256    init_test(cx, |_| {});
 5257    let mut cx = EditorTestContext::new(cx).await;
 5258
 5259    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5260    cx.update_editor(|editor, _, cx| {
 5261        editor.set_hard_wrap(Some(14), cx);
 5262    });
 5263
 5264    cx.set_state(indoc!(
 5265        "
 5266        one two three ˇ
 5267        "
 5268    ));
 5269    cx.simulate_input("four");
 5270    cx.run_until_parked();
 5271
 5272    cx.assert_editor_state(indoc!(
 5273        "
 5274        one two three
 5275        fourˇ
 5276        "
 5277    ));
 5278
 5279    cx.update_editor(|editor, window, cx| {
 5280        editor.newline(&Default::default(), window, cx);
 5281    });
 5282    cx.run_until_parked();
 5283    cx.assert_editor_state(indoc!(
 5284        "
 5285        one two three
 5286        four
 5287        ˇ
 5288        "
 5289    ));
 5290
 5291    cx.simulate_input("five");
 5292    cx.run_until_parked();
 5293    cx.assert_editor_state(indoc!(
 5294        "
 5295        one two three
 5296        four
 5297        fiveˇ
 5298        "
 5299    ));
 5300
 5301    cx.update_editor(|editor, window, cx| {
 5302        editor.newline(&Default::default(), window, cx);
 5303    });
 5304    cx.run_until_parked();
 5305    cx.simulate_input("# ");
 5306    cx.run_until_parked();
 5307    cx.assert_editor_state(indoc!(
 5308        "
 5309        one two three
 5310        four
 5311        five
 5312        # ˇ
 5313        "
 5314    ));
 5315
 5316    cx.update_editor(|editor, window, cx| {
 5317        editor.newline(&Default::default(), window, cx);
 5318    });
 5319    cx.run_until_parked();
 5320    cx.assert_editor_state(indoc!(
 5321        "
 5322        one two three
 5323        four
 5324        five
 5325        #\x20
 5326 5327        "
 5328    ));
 5329
 5330    cx.simulate_input(" 6");
 5331    cx.run_until_parked();
 5332    cx.assert_editor_state(indoc!(
 5333        "
 5334        one two three
 5335        four
 5336        five
 5337        #
 5338        # 6ˇ
 5339        "
 5340    ));
 5341}
 5342
 5343#[gpui::test]
 5344async fn test_clipboard(cx: &mut TestAppContext) {
 5345    init_test(cx, |_| {});
 5346
 5347    let mut cx = EditorTestContext::new(cx).await;
 5348
 5349    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5350    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5351    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5352
 5353    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5354    cx.set_state("two ˇfour ˇsix ˇ");
 5355    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5356    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5357
 5358    // Paste again but with only two cursors. Since the number of cursors doesn't
 5359    // match the number of slices in the clipboard, the entire clipboard text
 5360    // is pasted at each cursor.
 5361    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5362    cx.update_editor(|e, window, cx| {
 5363        e.handle_input("( ", window, cx);
 5364        e.paste(&Paste, window, cx);
 5365        e.handle_input(") ", window, cx);
 5366    });
 5367    cx.assert_editor_state(
 5368        &([
 5369            "( one✅ ",
 5370            "three ",
 5371            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5372            "three ",
 5373            "five ) ˇ",
 5374        ]
 5375        .join("\n")),
 5376    );
 5377
 5378    // Cut with three selections, one of which is full-line.
 5379    cx.set_state(indoc! {"
 5380        1«2ˇ»3
 5381        4ˇ567
 5382        «8ˇ»9"});
 5383    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5384    cx.assert_editor_state(indoc! {"
 5385        1ˇ3
 5386        ˇ9"});
 5387
 5388    // Paste with three selections, noticing how the copied selection that was full-line
 5389    // gets inserted before the second cursor.
 5390    cx.set_state(indoc! {"
 5391        1ˇ3
 5392 5393        «oˇ»ne"});
 5394    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5395    cx.assert_editor_state(indoc! {"
 5396        12ˇ3
 5397        4567
 5398 5399        8ˇne"});
 5400
 5401    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5402    cx.set_state(indoc! {"
 5403        The quick brown
 5404        fox juˇmps over
 5405        the lazy dog"});
 5406    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5407    assert_eq!(
 5408        cx.read_from_clipboard()
 5409            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5410        Some("fox jumps over\n".to_string())
 5411    );
 5412
 5413    // Paste with three selections, noticing how the copied full-line selection is inserted
 5414    // before the empty selections but replaces the selection that is non-empty.
 5415    cx.set_state(indoc! {"
 5416        Tˇhe quick brown
 5417        «foˇ»x jumps over
 5418        tˇhe lazy dog"});
 5419    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5420    cx.assert_editor_state(indoc! {"
 5421        fox jumps over
 5422        Tˇhe quick brown
 5423        fox jumps over
 5424        ˇx jumps over
 5425        fox jumps over
 5426        tˇhe lazy dog"});
 5427}
 5428
 5429#[gpui::test]
 5430async fn test_copy_trim(cx: &mut TestAppContext) {
 5431    init_test(cx, |_| {});
 5432
 5433    let mut cx = EditorTestContext::new(cx).await;
 5434    cx.set_state(
 5435        r#"            «for selection in selections.iter() {
 5436            let mut start = selection.start;
 5437            let mut end = selection.end;
 5438            let is_entire_line = selection.is_empty();
 5439            if is_entire_line {
 5440                start = Point::new(start.row, 0);ˇ»
 5441                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5442            }
 5443        "#,
 5444    );
 5445    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5446    assert_eq!(
 5447        cx.read_from_clipboard()
 5448            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5449        Some(
 5450            "for selection in selections.iter() {
 5451            let mut start = selection.start;
 5452            let mut end = selection.end;
 5453            let is_entire_line = selection.is_empty();
 5454            if is_entire_line {
 5455                start = Point::new(start.row, 0);"
 5456                .to_string()
 5457        ),
 5458        "Regular copying preserves all indentation selected",
 5459    );
 5460    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5461    assert_eq!(
 5462        cx.read_from_clipboard()
 5463            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5464        Some(
 5465            "for selection in selections.iter() {
 5466let mut start = selection.start;
 5467let mut end = selection.end;
 5468let is_entire_line = selection.is_empty();
 5469if is_entire_line {
 5470    start = Point::new(start.row, 0);"
 5471                .to_string()
 5472        ),
 5473        "Copying with stripping should strip all leading whitespaces"
 5474    );
 5475
 5476    cx.set_state(
 5477        r#"       «     for selection in selections.iter() {
 5478            let mut start = selection.start;
 5479            let mut end = selection.end;
 5480            let is_entire_line = selection.is_empty();
 5481            if is_entire_line {
 5482                start = Point::new(start.row, 0);ˇ»
 5483                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5484            }
 5485        "#,
 5486    );
 5487    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5488    assert_eq!(
 5489        cx.read_from_clipboard()
 5490            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5491        Some(
 5492            "     for selection in selections.iter() {
 5493            let mut start = selection.start;
 5494            let mut end = selection.end;
 5495            let is_entire_line = selection.is_empty();
 5496            if is_entire_line {
 5497                start = Point::new(start.row, 0);"
 5498                .to_string()
 5499        ),
 5500        "Regular copying preserves all indentation selected",
 5501    );
 5502    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5503    assert_eq!(
 5504        cx.read_from_clipboard()
 5505            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5506        Some(
 5507            "for selection in selections.iter() {
 5508let mut start = selection.start;
 5509let mut end = selection.end;
 5510let is_entire_line = selection.is_empty();
 5511if is_entire_line {
 5512    start = Point::new(start.row, 0);"
 5513                .to_string()
 5514        ),
 5515        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5516    );
 5517
 5518    cx.set_state(
 5519        r#"       «ˇ     for selection in selections.iter() {
 5520            let mut start = selection.start;
 5521            let mut end = selection.end;
 5522            let is_entire_line = selection.is_empty();
 5523            if is_entire_line {
 5524                start = Point::new(start.row, 0);»
 5525                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5526            }
 5527        "#,
 5528    );
 5529    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5530    assert_eq!(
 5531        cx.read_from_clipboard()
 5532            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5533        Some(
 5534            "     for selection in selections.iter() {
 5535            let mut start = selection.start;
 5536            let mut end = selection.end;
 5537            let is_entire_line = selection.is_empty();
 5538            if is_entire_line {
 5539                start = Point::new(start.row, 0);"
 5540                .to_string()
 5541        ),
 5542        "Regular copying for reverse selection works the same",
 5543    );
 5544    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5545    assert_eq!(
 5546        cx.read_from_clipboard()
 5547            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5548        Some(
 5549            "for selection in selections.iter() {
 5550let mut start = selection.start;
 5551let mut end = selection.end;
 5552let is_entire_line = selection.is_empty();
 5553if is_entire_line {
 5554    start = Point::new(start.row, 0);"
 5555                .to_string()
 5556        ),
 5557        "Copying with stripping for reverse selection works the same"
 5558    );
 5559
 5560    cx.set_state(
 5561        r#"            for selection «in selections.iter() {
 5562            let mut start = selection.start;
 5563            let mut end = selection.end;
 5564            let is_entire_line = selection.is_empty();
 5565            if is_entire_line {
 5566                start = Point::new(start.row, 0);ˇ»
 5567                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5568            }
 5569        "#,
 5570    );
 5571    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5572    assert_eq!(
 5573        cx.read_from_clipboard()
 5574            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5575        Some(
 5576            "in selections.iter() {
 5577            let mut start = selection.start;
 5578            let mut end = selection.end;
 5579            let is_entire_line = selection.is_empty();
 5580            if is_entire_line {
 5581                start = Point::new(start.row, 0);"
 5582                .to_string()
 5583        ),
 5584        "When selecting past the indent, the copying works as usual",
 5585    );
 5586    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5587    assert_eq!(
 5588        cx.read_from_clipboard()
 5589            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5590        Some(
 5591            "in selections.iter() {
 5592            let mut start = selection.start;
 5593            let mut end = selection.end;
 5594            let is_entire_line = selection.is_empty();
 5595            if is_entire_line {
 5596                start = Point::new(start.row, 0);"
 5597                .to_string()
 5598        ),
 5599        "When selecting past the indent, nothing is trimmed"
 5600    );
 5601
 5602    cx.set_state(
 5603        r#"            «for selection in selections.iter() {
 5604            let mut start = selection.start;
 5605
 5606            let mut end = selection.end;
 5607            let is_entire_line = selection.is_empty();
 5608            if is_entire_line {
 5609                start = Point::new(start.row, 0);
 5610ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5611            }
 5612        "#,
 5613    );
 5614    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5615    assert_eq!(
 5616        cx.read_from_clipboard()
 5617            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5618        Some(
 5619            "for selection in selections.iter() {
 5620let mut start = selection.start;
 5621
 5622let mut end = selection.end;
 5623let is_entire_line = selection.is_empty();
 5624if is_entire_line {
 5625    start = Point::new(start.row, 0);
 5626"
 5627            .to_string()
 5628        ),
 5629        "Copying with stripping should ignore empty lines"
 5630    );
 5631}
 5632
 5633#[gpui::test]
 5634async fn test_paste_multiline(cx: &mut TestAppContext) {
 5635    init_test(cx, |_| {});
 5636
 5637    let mut cx = EditorTestContext::new(cx).await;
 5638    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5639
 5640    // Cut an indented block, without the leading whitespace.
 5641    cx.set_state(indoc! {"
 5642        const a: B = (
 5643            c(),
 5644            «d(
 5645                e,
 5646                f
 5647            )ˇ»
 5648        );
 5649    "});
 5650    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5651    cx.assert_editor_state(indoc! {"
 5652        const a: B = (
 5653            c(),
 5654            ˇ
 5655        );
 5656    "});
 5657
 5658    // Paste it at the same position.
 5659    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5660    cx.assert_editor_state(indoc! {"
 5661        const a: B = (
 5662            c(),
 5663            d(
 5664                e,
 5665                f
 5666 5667        );
 5668    "});
 5669
 5670    // Paste it at a line with a lower indent level.
 5671    cx.set_state(indoc! {"
 5672        ˇ
 5673        const a: B = (
 5674            c(),
 5675        );
 5676    "});
 5677    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5678    cx.assert_editor_state(indoc! {"
 5679        d(
 5680            e,
 5681            f
 5682 5683        const a: B = (
 5684            c(),
 5685        );
 5686    "});
 5687
 5688    // Cut an indented block, with the leading whitespace.
 5689    cx.set_state(indoc! {"
 5690        const a: B = (
 5691            c(),
 5692        «    d(
 5693                e,
 5694                f
 5695            )
 5696        ˇ»);
 5697    "});
 5698    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5699    cx.assert_editor_state(indoc! {"
 5700        const a: B = (
 5701            c(),
 5702        ˇ);
 5703    "});
 5704
 5705    // Paste it at the same position.
 5706    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5707    cx.assert_editor_state(indoc! {"
 5708        const a: B = (
 5709            c(),
 5710            d(
 5711                e,
 5712                f
 5713            )
 5714        ˇ);
 5715    "});
 5716
 5717    // Paste it at a line with a higher indent level.
 5718    cx.set_state(indoc! {"
 5719        const a: B = (
 5720            c(),
 5721            d(
 5722                e,
 5723 5724            )
 5725        );
 5726    "});
 5727    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5728    cx.assert_editor_state(indoc! {"
 5729        const a: B = (
 5730            c(),
 5731            d(
 5732                e,
 5733                f    d(
 5734                    e,
 5735                    f
 5736                )
 5737        ˇ
 5738            )
 5739        );
 5740    "});
 5741
 5742    // Copy an indented block, starting mid-line
 5743    cx.set_state(indoc! {"
 5744        const a: B = (
 5745            c(),
 5746            somethin«g(
 5747                e,
 5748                f
 5749            )ˇ»
 5750        );
 5751    "});
 5752    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5753
 5754    // Paste it on a line with a lower indent level
 5755    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5756    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5757    cx.assert_editor_state(indoc! {"
 5758        const a: B = (
 5759            c(),
 5760            something(
 5761                e,
 5762                f
 5763            )
 5764        );
 5765        g(
 5766            e,
 5767            f
 5768"});
 5769}
 5770
 5771#[gpui::test]
 5772async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5773    init_test(cx, |_| {});
 5774
 5775    cx.write_to_clipboard(ClipboardItem::new_string(
 5776        "    d(\n        e\n    );\n".into(),
 5777    ));
 5778
 5779    let mut cx = EditorTestContext::new(cx).await;
 5780    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5781
 5782    cx.set_state(indoc! {"
 5783        fn a() {
 5784            b();
 5785            if c() {
 5786                ˇ
 5787            }
 5788        }
 5789    "});
 5790
 5791    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5792    cx.assert_editor_state(indoc! {"
 5793        fn a() {
 5794            b();
 5795            if c() {
 5796                d(
 5797                    e
 5798                );
 5799        ˇ
 5800            }
 5801        }
 5802    "});
 5803
 5804    cx.set_state(indoc! {"
 5805        fn a() {
 5806            b();
 5807            ˇ
 5808        }
 5809    "});
 5810
 5811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5812    cx.assert_editor_state(indoc! {"
 5813        fn a() {
 5814            b();
 5815            d(
 5816                e
 5817            );
 5818        ˇ
 5819        }
 5820    "});
 5821}
 5822
 5823#[gpui::test]
 5824fn test_select_all(cx: &mut TestAppContext) {
 5825    init_test(cx, |_| {});
 5826
 5827    let editor = cx.add_window(|window, cx| {
 5828        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5829        build_editor(buffer, window, cx)
 5830    });
 5831    _ = editor.update(cx, |editor, window, cx| {
 5832        editor.select_all(&SelectAll, window, cx);
 5833        assert_eq!(
 5834            editor.selections.display_ranges(cx),
 5835            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5836        );
 5837    });
 5838}
 5839
 5840#[gpui::test]
 5841fn test_select_line(cx: &mut TestAppContext) {
 5842    init_test(cx, |_| {});
 5843
 5844    let editor = cx.add_window(|window, cx| {
 5845        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5846        build_editor(buffer, window, cx)
 5847    });
 5848    _ = editor.update(cx, |editor, window, cx| {
 5849        editor.change_selections(None, window, cx, |s| {
 5850            s.select_display_ranges([
 5851                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5852                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5853                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5854                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5855            ])
 5856        });
 5857        editor.select_line(&SelectLine, window, cx);
 5858        assert_eq!(
 5859            editor.selections.display_ranges(cx),
 5860            vec![
 5861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5862                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5863            ]
 5864        );
 5865    });
 5866
 5867    _ = editor.update(cx, |editor, window, cx| {
 5868        editor.select_line(&SelectLine, window, cx);
 5869        assert_eq!(
 5870            editor.selections.display_ranges(cx),
 5871            vec![
 5872                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5873                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5874            ]
 5875        );
 5876    });
 5877
 5878    _ = editor.update(cx, |editor, window, cx| {
 5879        editor.select_line(&SelectLine, window, cx);
 5880        assert_eq!(
 5881            editor.selections.display_ranges(cx),
 5882            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5883        );
 5884    });
 5885}
 5886
 5887#[gpui::test]
 5888async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890    let mut cx = EditorTestContext::new(cx).await;
 5891
 5892    #[track_caller]
 5893    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5894        cx.set_state(initial_state);
 5895        cx.update_editor(|e, window, cx| {
 5896            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5897        });
 5898        cx.assert_editor_state(expected_state);
 5899    }
 5900
 5901    // Selection starts and ends at the middle of lines, left-to-right
 5902    test(
 5903        &mut cx,
 5904        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5905        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5906    );
 5907    // Same thing, right-to-left
 5908    test(
 5909        &mut cx,
 5910        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5911        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5912    );
 5913
 5914    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5915    test(
 5916        &mut cx,
 5917        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5918        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5919    );
 5920    // Same thing, right-to-left
 5921    test(
 5922        &mut cx,
 5923        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5924        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5925    );
 5926
 5927    // Whole buffer, left-to-right, last line ends with newline
 5928    test(
 5929        &mut cx,
 5930        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5931        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5932    );
 5933    // Same thing, right-to-left
 5934    test(
 5935        &mut cx,
 5936        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5937        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5938    );
 5939
 5940    // Starts at the end of a line, ends at the start of another
 5941    test(
 5942        &mut cx,
 5943        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5944        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5945    );
 5946}
 5947
 5948#[gpui::test]
 5949async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5950    init_test(cx, |_| {});
 5951
 5952    let editor = cx.add_window(|window, cx| {
 5953        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5954        build_editor(buffer, window, cx)
 5955    });
 5956
 5957    // setup
 5958    _ = editor.update(cx, |editor, window, cx| {
 5959        editor.fold_creases(
 5960            vec![
 5961                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5962                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5963                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5964            ],
 5965            true,
 5966            window,
 5967            cx,
 5968        );
 5969        assert_eq!(
 5970            editor.display_text(cx),
 5971            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5972        );
 5973    });
 5974
 5975    _ = editor.update(cx, |editor, window, cx| {
 5976        editor.change_selections(None, window, cx, |s| {
 5977            s.select_display_ranges([
 5978                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5979                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5980                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5981                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5982            ])
 5983        });
 5984        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5985        assert_eq!(
 5986            editor.display_text(cx),
 5987            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5988        );
 5989    });
 5990    EditorTestContext::for_editor(editor, cx)
 5991        .await
 5992        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5993
 5994    _ = editor.update(cx, |editor, window, cx| {
 5995        editor.change_selections(None, window, cx, |s| {
 5996            s.select_display_ranges([
 5997                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5998            ])
 5999        });
 6000        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6001        assert_eq!(
 6002            editor.display_text(cx),
 6003            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6004        );
 6005        assert_eq!(
 6006            editor.selections.display_ranges(cx),
 6007            [
 6008                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6009                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6010                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6011                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6012                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6013                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6014                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6015            ]
 6016        );
 6017    });
 6018    EditorTestContext::for_editor(editor, cx)
 6019        .await
 6020        .assert_editor_state(
 6021            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6022        );
 6023}
 6024
 6025#[gpui::test]
 6026async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6027    init_test(cx, |_| {});
 6028
 6029    let mut cx = EditorTestContext::new(cx).await;
 6030
 6031    cx.set_state(indoc!(
 6032        r#"abc
 6033           defˇghi
 6034
 6035           jk
 6036           nlmo
 6037           "#
 6038    ));
 6039
 6040    cx.update_editor(|editor, window, cx| {
 6041        editor.add_selection_above(&Default::default(), window, cx);
 6042    });
 6043
 6044    cx.assert_editor_state(indoc!(
 6045        r#"abcˇ
 6046           defˇghi
 6047
 6048           jk
 6049           nlmo
 6050           "#
 6051    ));
 6052
 6053    cx.update_editor(|editor, window, cx| {
 6054        editor.add_selection_above(&Default::default(), window, cx);
 6055    });
 6056
 6057    cx.assert_editor_state(indoc!(
 6058        r#"abcˇ
 6059            defˇghi
 6060
 6061            jk
 6062            nlmo
 6063            "#
 6064    ));
 6065
 6066    cx.update_editor(|editor, window, cx| {
 6067        editor.add_selection_below(&Default::default(), window, cx);
 6068    });
 6069
 6070    cx.assert_editor_state(indoc!(
 6071        r#"abc
 6072           defˇghi
 6073
 6074           jk
 6075           nlmo
 6076           "#
 6077    ));
 6078
 6079    cx.update_editor(|editor, window, cx| {
 6080        editor.undo_selection(&Default::default(), window, cx);
 6081    });
 6082
 6083    cx.assert_editor_state(indoc!(
 6084        r#"abcˇ
 6085           defˇghi
 6086
 6087           jk
 6088           nlmo
 6089           "#
 6090    ));
 6091
 6092    cx.update_editor(|editor, window, cx| {
 6093        editor.redo_selection(&Default::default(), window, cx);
 6094    });
 6095
 6096    cx.assert_editor_state(indoc!(
 6097        r#"abc
 6098           defˇghi
 6099
 6100           jk
 6101           nlmo
 6102           "#
 6103    ));
 6104
 6105    cx.update_editor(|editor, window, cx| {
 6106        editor.add_selection_below(&Default::default(), window, cx);
 6107    });
 6108
 6109    cx.assert_editor_state(indoc!(
 6110        r#"abc
 6111           defˇghi
 6112           ˇ
 6113           jk
 6114           nlmo
 6115           "#
 6116    ));
 6117
 6118    cx.update_editor(|editor, window, cx| {
 6119        editor.add_selection_below(&Default::default(), window, cx);
 6120    });
 6121
 6122    cx.assert_editor_state(indoc!(
 6123        r#"abc
 6124           defˇghi
 6125           ˇ
 6126           jkˇ
 6127           nlmo
 6128           "#
 6129    ));
 6130
 6131    cx.update_editor(|editor, window, cx| {
 6132        editor.add_selection_below(&Default::default(), window, cx);
 6133    });
 6134
 6135    cx.assert_editor_state(indoc!(
 6136        r#"abc
 6137           defˇghi
 6138           ˇ
 6139           jkˇ
 6140           nlmˇo
 6141           "#
 6142    ));
 6143
 6144    cx.update_editor(|editor, window, cx| {
 6145        editor.add_selection_below(&Default::default(), window, cx);
 6146    });
 6147
 6148    cx.assert_editor_state(indoc!(
 6149        r#"abc
 6150           defˇghi
 6151           ˇ
 6152           jkˇ
 6153           nlmˇo
 6154           ˇ"#
 6155    ));
 6156
 6157    // change selections
 6158    cx.set_state(indoc!(
 6159        r#"abc
 6160           def«ˇg»hi
 6161
 6162           jk
 6163           nlmo
 6164           "#
 6165    ));
 6166
 6167    cx.update_editor(|editor, window, cx| {
 6168        editor.add_selection_below(&Default::default(), window, cx);
 6169    });
 6170
 6171    cx.assert_editor_state(indoc!(
 6172        r#"abc
 6173           def«ˇg»hi
 6174
 6175           jk
 6176           nlm«ˇo»
 6177           "#
 6178    ));
 6179
 6180    cx.update_editor(|editor, window, cx| {
 6181        editor.add_selection_below(&Default::default(), window, cx);
 6182    });
 6183
 6184    cx.assert_editor_state(indoc!(
 6185        r#"abc
 6186           def«ˇg»hi
 6187
 6188           jk
 6189           nlm«ˇo»
 6190           "#
 6191    ));
 6192
 6193    cx.update_editor(|editor, window, cx| {
 6194        editor.add_selection_above(&Default::default(), window, cx);
 6195    });
 6196
 6197    cx.assert_editor_state(indoc!(
 6198        r#"abc
 6199           def«ˇg»hi
 6200
 6201           jk
 6202           nlmo
 6203           "#
 6204    ));
 6205
 6206    cx.update_editor(|editor, window, cx| {
 6207        editor.add_selection_above(&Default::default(), window, cx);
 6208    });
 6209
 6210    cx.assert_editor_state(indoc!(
 6211        r#"abc
 6212           def«ˇg»hi
 6213
 6214           jk
 6215           nlmo
 6216           "#
 6217    ));
 6218
 6219    // Change selections again
 6220    cx.set_state(indoc!(
 6221        r#"a«bc
 6222           defgˇ»hi
 6223
 6224           jk
 6225           nlmo
 6226           "#
 6227    ));
 6228
 6229    cx.update_editor(|editor, window, cx| {
 6230        editor.add_selection_below(&Default::default(), window, cx);
 6231    });
 6232
 6233    cx.assert_editor_state(indoc!(
 6234        r#"a«bcˇ»
 6235           d«efgˇ»hi
 6236
 6237           j«kˇ»
 6238           nlmo
 6239           "#
 6240    ));
 6241
 6242    cx.update_editor(|editor, window, cx| {
 6243        editor.add_selection_below(&Default::default(), window, cx);
 6244    });
 6245    cx.assert_editor_state(indoc!(
 6246        r#"a«bcˇ»
 6247           d«efgˇ»hi
 6248
 6249           j«kˇ»
 6250           n«lmoˇ»
 6251           "#
 6252    ));
 6253    cx.update_editor(|editor, window, cx| {
 6254        editor.add_selection_above(&Default::default(), window, cx);
 6255    });
 6256
 6257    cx.assert_editor_state(indoc!(
 6258        r#"a«bcˇ»
 6259           d«efgˇ»hi
 6260
 6261           j«kˇ»
 6262           nlmo
 6263           "#
 6264    ));
 6265
 6266    // Change selections again
 6267    cx.set_state(indoc!(
 6268        r#"abc
 6269           d«ˇefghi
 6270
 6271           jk
 6272           nlm»o
 6273           "#
 6274    ));
 6275
 6276    cx.update_editor(|editor, window, cx| {
 6277        editor.add_selection_above(&Default::default(), window, cx);
 6278    });
 6279
 6280    cx.assert_editor_state(indoc!(
 6281        r#"a«ˇbc»
 6282           d«ˇef»ghi
 6283
 6284           j«ˇk»
 6285           n«ˇlm»o
 6286           "#
 6287    ));
 6288
 6289    cx.update_editor(|editor, window, cx| {
 6290        editor.add_selection_below(&Default::default(), window, cx);
 6291    });
 6292
 6293    cx.assert_editor_state(indoc!(
 6294        r#"abc
 6295           d«ˇef»ghi
 6296
 6297           j«ˇk»
 6298           n«ˇlm»o
 6299           "#
 6300    ));
 6301}
 6302
 6303#[gpui::test]
 6304async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306    let mut cx = EditorTestContext::new(cx).await;
 6307
 6308    cx.set_state(indoc!(
 6309        r#"line onˇe
 6310           liˇne two
 6311           line three
 6312           line four"#
 6313    ));
 6314
 6315    cx.update_editor(|editor, window, cx| {
 6316        editor.add_selection_below(&Default::default(), window, cx);
 6317    });
 6318
 6319    // test multiple cursors expand in the same direction
 6320    cx.assert_editor_state(indoc!(
 6321        r#"line onˇe
 6322           liˇne twˇo
 6323           liˇne three
 6324           line four"#
 6325    ));
 6326
 6327    cx.update_editor(|editor, window, cx| {
 6328        editor.add_selection_below(&Default::default(), window, cx);
 6329    });
 6330
 6331    cx.update_editor(|editor, window, cx| {
 6332        editor.add_selection_below(&Default::default(), window, cx);
 6333    });
 6334
 6335    // test multiple cursors expand below overflow
 6336    cx.assert_editor_state(indoc!(
 6337        r#"line onˇe
 6338           liˇne twˇo
 6339           liˇne thˇree
 6340           liˇne foˇur"#
 6341    ));
 6342
 6343    cx.update_editor(|editor, window, cx| {
 6344        editor.add_selection_above(&Default::default(), window, cx);
 6345    });
 6346
 6347    // test multiple cursors retrieves back correctly
 6348    cx.assert_editor_state(indoc!(
 6349        r#"line onˇe
 6350           liˇne twˇo
 6351           liˇne thˇree
 6352           line four"#
 6353    ));
 6354
 6355    cx.update_editor(|editor, window, cx| {
 6356        editor.add_selection_above(&Default::default(), window, cx);
 6357    });
 6358
 6359    cx.update_editor(|editor, window, cx| {
 6360        editor.add_selection_above(&Default::default(), window, cx);
 6361    });
 6362
 6363    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6364    cx.assert_editor_state(indoc!(
 6365        r#"liˇne onˇe
 6366           liˇne two
 6367           line three
 6368           line four"#
 6369    ));
 6370
 6371    cx.update_editor(|editor, window, cx| {
 6372        editor.undo_selection(&Default::default(), window, cx);
 6373    });
 6374
 6375    // test undo
 6376    cx.assert_editor_state(indoc!(
 6377        r#"line onˇe
 6378           liˇne twˇo
 6379           line three
 6380           line four"#
 6381    ));
 6382
 6383    cx.update_editor(|editor, window, cx| {
 6384        editor.redo_selection(&Default::default(), window, cx);
 6385    });
 6386
 6387    // test redo
 6388    cx.assert_editor_state(indoc!(
 6389        r#"liˇne onˇe
 6390           liˇne two
 6391           line three
 6392           line four"#
 6393    ));
 6394
 6395    cx.set_state(indoc!(
 6396        r#"abcd
 6397           ef«ghˇ»
 6398           ijkl
 6399           «mˇ»nop"#
 6400    ));
 6401
 6402    cx.update_editor(|editor, window, cx| {
 6403        editor.add_selection_above(&Default::default(), window, cx);
 6404    });
 6405
 6406    // test multiple selections expand in the same direction
 6407    cx.assert_editor_state(indoc!(
 6408        r#"ab«cdˇ»
 6409           ef«ghˇ»
 6410           «iˇ»jkl
 6411           «mˇ»nop"#
 6412    ));
 6413
 6414    cx.update_editor(|editor, window, cx| {
 6415        editor.add_selection_above(&Default::default(), window, cx);
 6416    });
 6417
 6418    // test multiple selection upward overflow
 6419    cx.assert_editor_state(indoc!(
 6420        r#"ab«cdˇ»
 6421           «eˇ»f«ghˇ»
 6422           «iˇ»jkl
 6423           «mˇ»nop"#
 6424    ));
 6425
 6426    cx.update_editor(|editor, window, cx| {
 6427        editor.add_selection_below(&Default::default(), window, cx);
 6428    });
 6429
 6430    // test multiple selection retrieves back correctly
 6431    cx.assert_editor_state(indoc!(
 6432        r#"abcd
 6433           ef«ghˇ»
 6434           «iˇ»jkl
 6435           «mˇ»nop"#
 6436    ));
 6437
 6438    cx.update_editor(|editor, window, cx| {
 6439        editor.add_selection_below(&Default::default(), window, cx);
 6440    });
 6441
 6442    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6443    cx.assert_editor_state(indoc!(
 6444        r#"abcd
 6445           ef«ghˇ»
 6446           ij«klˇ»
 6447           «mˇ»nop"#
 6448    ));
 6449
 6450    cx.update_editor(|editor, window, cx| {
 6451        editor.undo_selection(&Default::default(), window, cx);
 6452    });
 6453
 6454    // test undo
 6455    cx.assert_editor_state(indoc!(
 6456        r#"abcd
 6457           ef«ghˇ»
 6458           «iˇ»jkl
 6459           «mˇ»nop"#
 6460    ));
 6461
 6462    cx.update_editor(|editor, window, cx| {
 6463        editor.redo_selection(&Default::default(), window, cx);
 6464    });
 6465
 6466    // test redo
 6467    cx.assert_editor_state(indoc!(
 6468        r#"abcd
 6469           ef«ghˇ»
 6470           ij«klˇ»
 6471           «mˇ»nop"#
 6472    ));
 6473}
 6474
 6475#[gpui::test]
 6476async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6477    init_test(cx, |_| {});
 6478    let mut cx = EditorTestContext::new(cx).await;
 6479
 6480    cx.set_state(indoc!(
 6481        r#"line onˇe
 6482           liˇne two
 6483           line three
 6484           line four"#
 6485    ));
 6486
 6487    cx.update_editor(|editor, window, cx| {
 6488        editor.add_selection_below(&Default::default(), window, cx);
 6489        editor.add_selection_below(&Default::default(), window, cx);
 6490        editor.add_selection_below(&Default::default(), window, cx);
 6491    });
 6492
 6493    // initial state with two multi cursor groups
 6494    cx.assert_editor_state(indoc!(
 6495        r#"line onˇe
 6496           liˇne twˇo
 6497           liˇne thˇree
 6498           liˇne foˇur"#
 6499    ));
 6500
 6501    // add single cursor in middle - simulate opt click
 6502    cx.update_editor(|editor, window, cx| {
 6503        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6504        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6505        editor.end_selection(window, cx);
 6506    });
 6507
 6508    cx.assert_editor_state(indoc!(
 6509        r#"line onˇe
 6510           liˇne twˇo
 6511           liˇneˇ thˇree
 6512           liˇne foˇur"#
 6513    ));
 6514
 6515    cx.update_editor(|editor, window, cx| {
 6516        editor.add_selection_above(&Default::default(), window, cx);
 6517    });
 6518
 6519    // test new added selection expands above and existing selection shrinks
 6520    cx.assert_editor_state(indoc!(
 6521        r#"line onˇe
 6522           liˇneˇ twˇo
 6523           liˇneˇ thˇree
 6524           line four"#
 6525    ));
 6526
 6527    cx.update_editor(|editor, window, cx| {
 6528        editor.add_selection_above(&Default::default(), window, cx);
 6529    });
 6530
 6531    // test new added selection expands above and existing selection shrinks
 6532    cx.assert_editor_state(indoc!(
 6533        r#"lineˇ onˇe
 6534           liˇneˇ twˇo
 6535           lineˇ three
 6536           line four"#
 6537    ));
 6538
 6539    // intial state with two selection groups
 6540    cx.set_state(indoc!(
 6541        r#"abcd
 6542           ef«ghˇ»
 6543           ijkl
 6544           «mˇ»nop"#
 6545    ));
 6546
 6547    cx.update_editor(|editor, window, cx| {
 6548        editor.add_selection_above(&Default::default(), window, cx);
 6549        editor.add_selection_above(&Default::default(), window, cx);
 6550    });
 6551
 6552    cx.assert_editor_state(indoc!(
 6553        r#"ab«cdˇ»
 6554           «eˇ»f«ghˇ»
 6555           «iˇ»jkl
 6556           «mˇ»nop"#
 6557    ));
 6558
 6559    // add single selection in middle - simulate opt drag
 6560    cx.update_editor(|editor, window, cx| {
 6561        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6562        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6563        editor.update_selection(
 6564            DisplayPoint::new(DisplayRow(2), 4),
 6565            0,
 6566            gpui::Point::<f32>::default(),
 6567            window,
 6568            cx,
 6569        );
 6570        editor.end_selection(window, cx);
 6571    });
 6572
 6573    cx.assert_editor_state(indoc!(
 6574        r#"ab«cdˇ»
 6575           «eˇ»f«ghˇ»
 6576           «iˇ»jk«lˇ»
 6577           «mˇ»nop"#
 6578    ));
 6579
 6580    cx.update_editor(|editor, window, cx| {
 6581        editor.add_selection_below(&Default::default(), window, cx);
 6582    });
 6583
 6584    // test new added selection expands below, others shrinks from above
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abcd
 6587           ef«ghˇ»
 6588           «iˇ»jk«lˇ»
 6589           «mˇ»no«pˇ»"#
 6590    ));
 6591}
 6592
 6593#[gpui::test]
 6594async fn test_select_next(cx: &mut TestAppContext) {
 6595    init_test(cx, |_| {});
 6596
 6597    let mut cx = EditorTestContext::new(cx).await;
 6598    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6599
 6600    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6601        .unwrap();
 6602    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6603
 6604    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6605        .unwrap();
 6606    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6607
 6608    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6609    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6610
 6611    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6612    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6613
 6614    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6615        .unwrap();
 6616    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6617
 6618    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6619        .unwrap();
 6620    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6621
 6622    // Test selection direction should be preserved
 6623    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6624
 6625    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6626        .unwrap();
 6627    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6628}
 6629
 6630#[gpui::test]
 6631async fn test_select_all_matches(cx: &mut TestAppContext) {
 6632    init_test(cx, |_| {});
 6633
 6634    let mut cx = EditorTestContext::new(cx).await;
 6635
 6636    // Test caret-only selections
 6637    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6638    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6639        .unwrap();
 6640    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6641
 6642    // Test left-to-right selections
 6643    cx.set_state("abc\n«abcˇ»\nabc");
 6644    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6645        .unwrap();
 6646    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6647
 6648    // Test right-to-left selections
 6649    cx.set_state("abc\n«ˇabc»\nabc");
 6650    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6651        .unwrap();
 6652    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6653
 6654    // Test selecting whitespace with caret selection
 6655    cx.set_state("abc\nˇ   abc\nabc");
 6656    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6657        .unwrap();
 6658    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6659
 6660    // Test selecting whitespace with left-to-right selection
 6661    cx.set_state("abc\n«ˇ  »abc\nabc");
 6662    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6663        .unwrap();
 6664    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6665
 6666    // Test no matches with right-to-left selection
 6667    cx.set_state("abc\n«  ˇ»abc\nabc");
 6668    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6669        .unwrap();
 6670    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6671}
 6672
 6673#[gpui::test]
 6674async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6675    init_test(cx, |_| {});
 6676
 6677    let mut cx = EditorTestContext::new(cx).await;
 6678
 6679    let large_body_1 = "\nd".repeat(200);
 6680    let large_body_2 = "\ne".repeat(200);
 6681
 6682    cx.set_state(&format!(
 6683        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6684    ));
 6685    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6686        let scroll_position = editor.scroll_position(cx);
 6687        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6688        scroll_position
 6689    });
 6690
 6691    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6692        .unwrap();
 6693    cx.assert_editor_state(&format!(
 6694        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6695    ));
 6696    let scroll_position_after_selection =
 6697        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6698    assert_eq!(
 6699        initial_scroll_position, scroll_position_after_selection,
 6700        "Scroll position should not change after selecting all matches"
 6701    );
 6702}
 6703
 6704#[gpui::test]
 6705async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6706    init_test(cx, |_| {});
 6707
 6708    let mut cx = EditorLspTestContext::new_rust(
 6709        lsp::ServerCapabilities {
 6710            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6711            ..Default::default()
 6712        },
 6713        cx,
 6714    )
 6715    .await;
 6716
 6717    cx.set_state(indoc! {"
 6718        line 1
 6719        line 2
 6720        linˇe 3
 6721        line 4
 6722        line 5
 6723    "});
 6724
 6725    // Make an edit
 6726    cx.update_editor(|editor, window, cx| {
 6727        editor.handle_input("X", window, cx);
 6728    });
 6729
 6730    // Move cursor to a different position
 6731    cx.update_editor(|editor, window, cx| {
 6732        editor.change_selections(None, window, cx, |s| {
 6733            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6734        });
 6735    });
 6736
 6737    cx.assert_editor_state(indoc! {"
 6738        line 1
 6739        line 2
 6740        linXe 3
 6741        line 4
 6742        liˇne 5
 6743    "});
 6744
 6745    cx.lsp
 6746        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6747            Ok(Some(vec![lsp::TextEdit::new(
 6748                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6749                "PREFIX ".to_string(),
 6750            )]))
 6751        });
 6752
 6753    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6754        .unwrap()
 6755        .await
 6756        .unwrap();
 6757
 6758    cx.assert_editor_state(indoc! {"
 6759        PREFIX line 1
 6760        line 2
 6761        linXe 3
 6762        line 4
 6763        liˇne 5
 6764    "});
 6765
 6766    // Undo formatting
 6767    cx.update_editor(|editor, window, cx| {
 6768        editor.undo(&Default::default(), window, cx);
 6769    });
 6770
 6771    // Verify cursor moved back to position after edit
 6772    cx.assert_editor_state(indoc! {"
 6773        line 1
 6774        line 2
 6775        linXˇe 3
 6776        line 4
 6777        line 5
 6778    "});
 6779}
 6780
 6781#[gpui::test]
 6782async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 6783    init_test(cx, |_| {});
 6784
 6785    let mut cx = EditorTestContext::new(cx).await;
 6786
 6787    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 6788    cx.update_editor(|editor, window, cx| {
 6789        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 6790    });
 6791
 6792    cx.set_state(indoc! {"
 6793        line 1
 6794        line 2
 6795        linˇe 3
 6796        line 4
 6797        line 5
 6798        line 6
 6799        line 7
 6800        line 8
 6801        line 9
 6802        line 10
 6803    "});
 6804
 6805    let snapshot = cx.buffer_snapshot();
 6806    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 6807
 6808    cx.update(|_, cx| {
 6809        provider.update(cx, |provider, _| {
 6810            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 6811                id: None,
 6812                edits: vec![(edit_position..edit_position, "X".into())],
 6813                edit_preview: None,
 6814            }))
 6815        })
 6816    });
 6817
 6818    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 6819    cx.update_editor(|editor, window, cx| {
 6820        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 6821    });
 6822
 6823    cx.assert_editor_state(indoc! {"
 6824        line 1
 6825        line 2
 6826        lineXˇ 3
 6827        line 4
 6828        line 5
 6829        line 6
 6830        line 7
 6831        line 8
 6832        line 9
 6833        line 10
 6834    "});
 6835
 6836    cx.update_editor(|editor, window, cx| {
 6837        editor.change_selections(None, window, cx, |s| {
 6838            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 6839        });
 6840    });
 6841
 6842    cx.assert_editor_state(indoc! {"
 6843        line 1
 6844        line 2
 6845        lineX 3
 6846        line 4
 6847        line 5
 6848        line 6
 6849        line 7
 6850        line 8
 6851        line 9
 6852        liˇne 10
 6853    "});
 6854
 6855    cx.update_editor(|editor, window, cx| {
 6856        editor.undo(&Default::default(), window, cx);
 6857    });
 6858
 6859    cx.assert_editor_state(indoc! {"
 6860        line 1
 6861        line 2
 6862        lineˇ 3
 6863        line 4
 6864        line 5
 6865        line 6
 6866        line 7
 6867        line 8
 6868        line 9
 6869        line 10
 6870    "});
 6871}
 6872
 6873#[gpui::test]
 6874async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 6875    init_test(cx, |_| {});
 6876
 6877    let mut cx = EditorTestContext::new(cx).await;
 6878    cx.set_state(
 6879        r#"let foo = 2;
 6880lˇet foo = 2;
 6881let fooˇ = 2;
 6882let foo = 2;
 6883let foo = ˇ2;"#,
 6884    );
 6885
 6886    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6887        .unwrap();
 6888    cx.assert_editor_state(
 6889        r#"let foo = 2;
 6890«letˇ» foo = 2;
 6891let «fooˇ» = 2;
 6892let foo = 2;
 6893let foo = «2ˇ»;"#,
 6894    );
 6895
 6896    // noop for multiple selections with different contents
 6897    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6898        .unwrap();
 6899    cx.assert_editor_state(
 6900        r#"let foo = 2;
 6901«letˇ» foo = 2;
 6902let «fooˇ» = 2;
 6903let foo = 2;
 6904let foo = «2ˇ»;"#,
 6905    );
 6906
 6907    // Test last selection direction should be preserved
 6908    cx.set_state(
 6909        r#"let foo = 2;
 6910let foo = 2;
 6911let «fooˇ» = 2;
 6912let «ˇfoo» = 2;
 6913let foo = 2;"#,
 6914    );
 6915
 6916    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6917        .unwrap();
 6918    cx.assert_editor_state(
 6919        r#"let foo = 2;
 6920let foo = 2;
 6921let «fooˇ» = 2;
 6922let «ˇfoo» = 2;
 6923let «ˇfoo» = 2;"#,
 6924    );
 6925}
 6926
 6927#[gpui::test]
 6928async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 6929    init_test(cx, |_| {});
 6930
 6931    let mut cx =
 6932        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 6933
 6934    cx.assert_editor_state(indoc! {"
 6935        ˇbbb
 6936        ccc
 6937
 6938        bbb
 6939        ccc
 6940        "});
 6941    cx.dispatch_action(SelectPrevious::default());
 6942    cx.assert_editor_state(indoc! {"
 6943                «bbbˇ»
 6944                ccc
 6945
 6946                bbb
 6947                ccc
 6948                "});
 6949    cx.dispatch_action(SelectPrevious::default());
 6950    cx.assert_editor_state(indoc! {"
 6951                «bbbˇ»
 6952                ccc
 6953
 6954                «bbbˇ»
 6955                ccc
 6956                "});
 6957}
 6958
 6959#[gpui::test]
 6960async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 6961    init_test(cx, |_| {});
 6962
 6963    let mut cx = EditorTestContext::new(cx).await;
 6964    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6965
 6966    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6967        .unwrap();
 6968    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6969
 6970    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6971        .unwrap();
 6972    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6973
 6974    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6975    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6976
 6977    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6978    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 6979
 6980    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6981        .unwrap();
 6982    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 6983
 6984    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6985        .unwrap();
 6986    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6987}
 6988
 6989#[gpui::test]
 6990async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 6991    init_test(cx, |_| {});
 6992
 6993    let mut cx = EditorTestContext::new(cx).await;
 6994    cx.set_state("");
 6995
 6996    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 6997        .unwrap();
 6998    cx.assert_editor_state("«aˇ»");
 6999    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7000        .unwrap();
 7001    cx.assert_editor_state("«aˇ»");
 7002}
 7003
 7004#[gpui::test]
 7005async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7006    init_test(cx, |_| {});
 7007
 7008    let mut cx = EditorTestContext::new(cx).await;
 7009    cx.set_state(
 7010        r#"let foo = 2;
 7011lˇet foo = 2;
 7012let fooˇ = 2;
 7013let foo = 2;
 7014let foo = ˇ2;"#,
 7015    );
 7016
 7017    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7018        .unwrap();
 7019    cx.assert_editor_state(
 7020        r#"let foo = 2;
 7021«letˇ» foo = 2;
 7022let «fooˇ» = 2;
 7023let foo = 2;
 7024let foo = «2ˇ»;"#,
 7025    );
 7026
 7027    // noop for multiple selections with different contents
 7028    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7029        .unwrap();
 7030    cx.assert_editor_state(
 7031        r#"let foo = 2;
 7032«letˇ» foo = 2;
 7033let «fooˇ» = 2;
 7034let foo = 2;
 7035let foo = «2ˇ»;"#,
 7036    );
 7037}
 7038
 7039#[gpui::test]
 7040async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7041    init_test(cx, |_| {});
 7042
 7043    let mut cx = EditorTestContext::new(cx).await;
 7044    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7045
 7046    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7047        .unwrap();
 7048    // selection direction is preserved
 7049    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7050
 7051    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7052        .unwrap();
 7053    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7054
 7055    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7056    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7057
 7058    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7059    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7060
 7061    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7062        .unwrap();
 7063    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7064
 7065    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7066        .unwrap();
 7067    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7068}
 7069
 7070#[gpui::test]
 7071async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7072    init_test(cx, |_| {});
 7073
 7074    let language = Arc::new(Language::new(
 7075        LanguageConfig::default(),
 7076        Some(tree_sitter_rust::LANGUAGE.into()),
 7077    ));
 7078
 7079    let text = r#"
 7080        use mod1::mod2::{mod3, mod4};
 7081
 7082        fn fn_1(param1: bool, param2: &str) {
 7083            let var1 = "text";
 7084        }
 7085    "#
 7086    .unindent();
 7087
 7088    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7089    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7090    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7091
 7092    editor
 7093        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7094        .await;
 7095
 7096    editor.update_in(cx, |editor, window, cx| {
 7097        editor.change_selections(None, window, cx, |s| {
 7098            s.select_display_ranges([
 7099                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7100                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7101                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7102            ]);
 7103        });
 7104        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7105    });
 7106    editor.update(cx, |editor, cx| {
 7107        assert_text_with_selections(
 7108            editor,
 7109            indoc! {r#"
 7110                use mod1::mod2::{mod3, «mod4ˇ»};
 7111
 7112                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7113                    let var1 = "«ˇtext»";
 7114                }
 7115            "#},
 7116            cx,
 7117        );
 7118    });
 7119
 7120    editor.update_in(cx, |editor, window, cx| {
 7121        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7122    });
 7123    editor.update(cx, |editor, cx| {
 7124        assert_text_with_selections(
 7125            editor,
 7126            indoc! {r#"
 7127                use mod1::mod2::«{mod3, mod4}ˇ»;
 7128
 7129                «ˇfn fn_1(param1: bool, param2: &str) {
 7130                    let var1 = "text";
 7131 7132            "#},
 7133            cx,
 7134        );
 7135    });
 7136
 7137    editor.update_in(cx, |editor, window, cx| {
 7138        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7139    });
 7140    assert_eq!(
 7141        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7142        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7143    );
 7144
 7145    // Trying to expand the selected syntax node one more time has no effect.
 7146    editor.update_in(cx, |editor, window, cx| {
 7147        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7148    });
 7149    assert_eq!(
 7150        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7151        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7152    );
 7153
 7154    editor.update_in(cx, |editor, window, cx| {
 7155        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7156    });
 7157    editor.update(cx, |editor, cx| {
 7158        assert_text_with_selections(
 7159            editor,
 7160            indoc! {r#"
 7161                use mod1::mod2::«{mod3, mod4}ˇ»;
 7162
 7163                «ˇfn fn_1(param1: bool, param2: &str) {
 7164                    let var1 = "text";
 7165 7166            "#},
 7167            cx,
 7168        );
 7169    });
 7170
 7171    editor.update_in(cx, |editor, window, cx| {
 7172        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7173    });
 7174    editor.update(cx, |editor, cx| {
 7175        assert_text_with_selections(
 7176            editor,
 7177            indoc! {r#"
 7178                use mod1::mod2::{mod3, «mod4ˇ»};
 7179
 7180                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7181                    let var1 = "«ˇtext»";
 7182                }
 7183            "#},
 7184            cx,
 7185        );
 7186    });
 7187
 7188    editor.update_in(cx, |editor, window, cx| {
 7189        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7190    });
 7191    editor.update(cx, |editor, cx| {
 7192        assert_text_with_selections(
 7193            editor,
 7194            indoc! {r#"
 7195                use mod1::mod2::{mod3, mo«ˇ»d4};
 7196
 7197                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7198                    let var1 = "te«ˇ»xt";
 7199                }
 7200            "#},
 7201            cx,
 7202        );
 7203    });
 7204
 7205    // Trying to shrink the selected syntax node one more time has no effect.
 7206    editor.update_in(cx, |editor, window, cx| {
 7207        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7208    });
 7209    editor.update_in(cx, |editor, _, cx| {
 7210        assert_text_with_selections(
 7211            editor,
 7212            indoc! {r#"
 7213                use mod1::mod2::{mod3, mo«ˇ»d4};
 7214
 7215                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7216                    let var1 = "te«ˇ»xt";
 7217                }
 7218            "#},
 7219            cx,
 7220        );
 7221    });
 7222
 7223    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7224    // a fold.
 7225    editor.update_in(cx, |editor, window, cx| {
 7226        editor.fold_creases(
 7227            vec![
 7228                Crease::simple(
 7229                    Point::new(0, 21)..Point::new(0, 24),
 7230                    FoldPlaceholder::test(),
 7231                ),
 7232                Crease::simple(
 7233                    Point::new(3, 20)..Point::new(3, 22),
 7234                    FoldPlaceholder::test(),
 7235                ),
 7236            ],
 7237            true,
 7238            window,
 7239            cx,
 7240        );
 7241        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7242    });
 7243    editor.update(cx, |editor, cx| {
 7244        assert_text_with_selections(
 7245            editor,
 7246            indoc! {r#"
 7247                use mod1::mod2::«{mod3, mod4}ˇ»;
 7248
 7249                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7250                    let var1 = "«ˇtext»";
 7251                }
 7252            "#},
 7253            cx,
 7254        );
 7255    });
 7256}
 7257
 7258#[gpui::test]
 7259async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7260    init_test(cx, |_| {});
 7261
 7262    let language = Arc::new(Language::new(
 7263        LanguageConfig::default(),
 7264        Some(tree_sitter_rust::LANGUAGE.into()),
 7265    ));
 7266
 7267    let text = "let a = 2;";
 7268
 7269    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7270    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7271    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7272
 7273    editor
 7274        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7275        .await;
 7276
 7277    // Test case 1: Cursor at end of word
 7278    editor.update_in(cx, |editor, window, cx| {
 7279        editor.change_selections(None, window, cx, |s| {
 7280            s.select_display_ranges([
 7281                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7282            ]);
 7283        });
 7284    });
 7285    editor.update(cx, |editor, cx| {
 7286        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7287    });
 7288    editor.update_in(cx, |editor, window, cx| {
 7289        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7290    });
 7291    editor.update(cx, |editor, cx| {
 7292        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7293    });
 7294    editor.update_in(cx, |editor, window, cx| {
 7295        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7296    });
 7297    editor.update(cx, |editor, cx| {
 7298        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7299    });
 7300
 7301    // Test case 2: Cursor at end of statement
 7302    editor.update_in(cx, |editor, window, cx| {
 7303        editor.change_selections(None, window, cx, |s| {
 7304            s.select_display_ranges([
 7305                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7306            ]);
 7307        });
 7308    });
 7309    editor.update(cx, |editor, cx| {
 7310        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7311    });
 7312    editor.update_in(cx, |editor, window, cx| {
 7313        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7314    });
 7315    editor.update(cx, |editor, cx| {
 7316        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7317    });
 7318}
 7319
 7320#[gpui::test]
 7321async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7322    init_test(cx, |_| {});
 7323
 7324    let language = Arc::new(Language::new(
 7325        LanguageConfig::default(),
 7326        Some(tree_sitter_rust::LANGUAGE.into()),
 7327    ));
 7328
 7329    let text = r#"
 7330        use mod1::mod2::{mod3, mod4};
 7331
 7332        fn fn_1(param1: bool, param2: &str) {
 7333            let var1 = "hello world";
 7334        }
 7335    "#
 7336    .unindent();
 7337
 7338    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7339    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7340    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7341
 7342    editor
 7343        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7344        .await;
 7345
 7346    // Test 1: Cursor on a letter of a string word
 7347    editor.update_in(cx, |editor, window, cx| {
 7348        editor.change_selections(None, window, cx, |s| {
 7349            s.select_display_ranges([
 7350                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7351            ]);
 7352        });
 7353    });
 7354    editor.update_in(cx, |editor, window, cx| {
 7355        assert_text_with_selections(
 7356            editor,
 7357            indoc! {r#"
 7358                use mod1::mod2::{mod3, mod4};
 7359
 7360                fn fn_1(param1: bool, param2: &str) {
 7361                    let var1 = "hˇello world";
 7362                }
 7363            "#},
 7364            cx,
 7365        );
 7366        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7367        assert_text_with_selections(
 7368            editor,
 7369            indoc! {r#"
 7370                use mod1::mod2::{mod3, mod4};
 7371
 7372                fn fn_1(param1: bool, param2: &str) {
 7373                    let var1 = "«ˇhello» world";
 7374                }
 7375            "#},
 7376            cx,
 7377        );
 7378    });
 7379
 7380    // Test 2: Partial selection within a word
 7381    editor.update_in(cx, |editor, window, cx| {
 7382        editor.change_selections(None, window, cx, |s| {
 7383            s.select_display_ranges([
 7384                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7385            ]);
 7386        });
 7387    });
 7388    editor.update_in(cx, |editor, window, cx| {
 7389        assert_text_with_selections(
 7390            editor,
 7391            indoc! {r#"
 7392                use mod1::mod2::{mod3, mod4};
 7393
 7394                fn fn_1(param1: bool, param2: &str) {
 7395                    let var1 = "h«elˇ»lo world";
 7396                }
 7397            "#},
 7398            cx,
 7399        );
 7400        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7401        assert_text_with_selections(
 7402            editor,
 7403            indoc! {r#"
 7404                use mod1::mod2::{mod3, mod4};
 7405
 7406                fn fn_1(param1: bool, param2: &str) {
 7407                    let var1 = "«ˇhello» world";
 7408                }
 7409            "#},
 7410            cx,
 7411        );
 7412    });
 7413
 7414    // Test 3: Complete word already selected
 7415    editor.update_in(cx, |editor, window, cx| {
 7416        editor.change_selections(None, window, cx, |s| {
 7417            s.select_display_ranges([
 7418                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7419            ]);
 7420        });
 7421    });
 7422    editor.update_in(cx, |editor, window, cx| {
 7423        assert_text_with_selections(
 7424            editor,
 7425            indoc! {r#"
 7426                use mod1::mod2::{mod3, mod4};
 7427
 7428                fn fn_1(param1: bool, param2: &str) {
 7429                    let var1 = "«helloˇ» world";
 7430                }
 7431            "#},
 7432            cx,
 7433        );
 7434        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7435        assert_text_with_selections(
 7436            editor,
 7437            indoc! {r#"
 7438                use mod1::mod2::{mod3, mod4};
 7439
 7440                fn fn_1(param1: bool, param2: &str) {
 7441                    let var1 = "«hello worldˇ»";
 7442                }
 7443            "#},
 7444            cx,
 7445        );
 7446    });
 7447
 7448    // Test 4: Selection spanning across words
 7449    editor.update_in(cx, |editor, window, cx| {
 7450        editor.change_selections(None, window, cx, |s| {
 7451            s.select_display_ranges([
 7452                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7453            ]);
 7454        });
 7455    });
 7456    editor.update_in(cx, |editor, window, cx| {
 7457        assert_text_with_selections(
 7458            editor,
 7459            indoc! {r#"
 7460                use mod1::mod2::{mod3, mod4};
 7461
 7462                fn fn_1(param1: bool, param2: &str) {
 7463                    let var1 = "hel«lo woˇ»rld";
 7464                }
 7465            "#},
 7466            cx,
 7467        );
 7468        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, 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 = "«ˇhello world»";
 7476                }
 7477            "#},
 7478            cx,
 7479        );
 7480    });
 7481
 7482    // Test 5: Expansion beyond string
 7483    editor.update_in(cx, |editor, window, cx| {
 7484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7485        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7486        assert_text_with_selections(
 7487            editor,
 7488            indoc! {r#"
 7489                use mod1::mod2::{mod3, mod4};
 7490
 7491                fn fn_1(param1: bool, param2: &str) {
 7492                    «ˇlet var1 = "hello world";»
 7493                }
 7494            "#},
 7495            cx,
 7496        );
 7497    });
 7498}
 7499
 7500#[gpui::test]
 7501async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7502    init_test(cx, |_| {});
 7503
 7504    let base_text = r#"
 7505        impl A {
 7506            // this is an uncommitted comment
 7507
 7508            fn b() {
 7509                c();
 7510            }
 7511
 7512            // this is another uncommitted comment
 7513
 7514            fn d() {
 7515                // e
 7516                // f
 7517            }
 7518        }
 7519
 7520        fn g() {
 7521            // h
 7522        }
 7523    "#
 7524    .unindent();
 7525
 7526    let text = r#"
 7527        ˇimpl A {
 7528
 7529            fn b() {
 7530                c();
 7531            }
 7532
 7533            fn d() {
 7534                // e
 7535                // f
 7536            }
 7537        }
 7538
 7539        fn g() {
 7540            // h
 7541        }
 7542    "#
 7543    .unindent();
 7544
 7545    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7546    cx.set_state(&text);
 7547    cx.set_head_text(&base_text);
 7548    cx.update_editor(|editor, window, cx| {
 7549        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7550    });
 7551
 7552    cx.assert_state_with_diff(
 7553        "
 7554        ˇimpl A {
 7555      -     // this is an uncommitted comment
 7556
 7557            fn b() {
 7558                c();
 7559            }
 7560
 7561      -     // this is another uncommitted comment
 7562      -
 7563            fn d() {
 7564                // e
 7565                // f
 7566            }
 7567        }
 7568
 7569        fn g() {
 7570            // h
 7571        }
 7572    "
 7573        .unindent(),
 7574    );
 7575
 7576    let expected_display_text = "
 7577        impl A {
 7578            // this is an uncommitted comment
 7579
 7580            fn b() {
 7581 7582            }
 7583
 7584            // this is another uncommitted comment
 7585
 7586            fn d() {
 7587 7588            }
 7589        }
 7590
 7591        fn g() {
 7592 7593        }
 7594        "
 7595    .unindent();
 7596
 7597    cx.update_editor(|editor, window, cx| {
 7598        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7599        assert_eq!(editor.display_text(cx), expected_display_text);
 7600    });
 7601}
 7602
 7603#[gpui::test]
 7604async fn test_autoindent(cx: &mut TestAppContext) {
 7605    init_test(cx, |_| {});
 7606
 7607    let language = Arc::new(
 7608        Language::new(
 7609            LanguageConfig {
 7610                brackets: BracketPairConfig {
 7611                    pairs: vec![
 7612                        BracketPair {
 7613                            start: "{".to_string(),
 7614                            end: "}".to_string(),
 7615                            close: false,
 7616                            surround: false,
 7617                            newline: true,
 7618                        },
 7619                        BracketPair {
 7620                            start: "(".to_string(),
 7621                            end: ")".to_string(),
 7622                            close: false,
 7623                            surround: false,
 7624                            newline: true,
 7625                        },
 7626                    ],
 7627                    ..Default::default()
 7628                },
 7629                ..Default::default()
 7630            },
 7631            Some(tree_sitter_rust::LANGUAGE.into()),
 7632        )
 7633        .with_indents_query(
 7634            r#"
 7635                (_ "(" ")" @end) @indent
 7636                (_ "{" "}" @end) @indent
 7637            "#,
 7638        )
 7639        .unwrap(),
 7640    );
 7641
 7642    let text = "fn a() {}";
 7643
 7644    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7645    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7646    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7647    editor
 7648        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7649        .await;
 7650
 7651    editor.update_in(cx, |editor, window, cx| {
 7652        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 7653        editor.newline(&Newline, window, cx);
 7654        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7655        assert_eq!(
 7656            editor.selections.ranges(cx),
 7657            &[
 7658                Point::new(1, 4)..Point::new(1, 4),
 7659                Point::new(3, 4)..Point::new(3, 4),
 7660                Point::new(5, 0)..Point::new(5, 0)
 7661            ]
 7662        );
 7663    });
 7664}
 7665
 7666#[gpui::test]
 7667async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7668    init_test(cx, |_| {});
 7669
 7670    {
 7671        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7672        cx.set_state(indoc! {"
 7673            impl A {
 7674
 7675                fn b() {}
 7676
 7677            «fn c() {
 7678
 7679            }ˇ»
 7680            }
 7681        "});
 7682
 7683        cx.update_editor(|editor, window, cx| {
 7684            editor.autoindent(&Default::default(), window, cx);
 7685        });
 7686
 7687        cx.assert_editor_state(indoc! {"
 7688            impl A {
 7689
 7690                fn b() {}
 7691
 7692                «fn c() {
 7693
 7694                }ˇ»
 7695            }
 7696        "});
 7697    }
 7698
 7699    {
 7700        let mut cx = EditorTestContext::new_multibuffer(
 7701            cx,
 7702            [indoc! { "
 7703                impl A {
 7704                «
 7705                // a
 7706                fn b(){}
 7707                »
 7708                «
 7709                    }
 7710                    fn c(){}
 7711                »
 7712            "}],
 7713        );
 7714
 7715        let buffer = cx.update_editor(|editor, _, cx| {
 7716            let buffer = editor.buffer().update(cx, |buffer, _| {
 7717                buffer.all_buffers().iter().next().unwrap().clone()
 7718            });
 7719            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7720            buffer
 7721        });
 7722
 7723        cx.run_until_parked();
 7724        cx.update_editor(|editor, window, cx| {
 7725            editor.select_all(&Default::default(), window, cx);
 7726            editor.autoindent(&Default::default(), window, cx)
 7727        });
 7728        cx.run_until_parked();
 7729
 7730        cx.update(|_, cx| {
 7731            assert_eq!(
 7732                buffer.read(cx).text(),
 7733                indoc! { "
 7734                    impl A {
 7735
 7736                        // a
 7737                        fn b(){}
 7738
 7739
 7740                    }
 7741                    fn c(){}
 7742
 7743                " }
 7744            )
 7745        });
 7746    }
 7747}
 7748
 7749#[gpui::test]
 7750async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7751    init_test(cx, |_| {});
 7752
 7753    let mut cx = EditorTestContext::new(cx).await;
 7754
 7755    let language = Arc::new(Language::new(
 7756        LanguageConfig {
 7757            brackets: BracketPairConfig {
 7758                pairs: vec![
 7759                    BracketPair {
 7760                        start: "{".to_string(),
 7761                        end: "}".to_string(),
 7762                        close: true,
 7763                        surround: true,
 7764                        newline: true,
 7765                    },
 7766                    BracketPair {
 7767                        start: "(".to_string(),
 7768                        end: ")".to_string(),
 7769                        close: true,
 7770                        surround: true,
 7771                        newline: true,
 7772                    },
 7773                    BracketPair {
 7774                        start: "/*".to_string(),
 7775                        end: " */".to_string(),
 7776                        close: true,
 7777                        surround: true,
 7778                        newline: true,
 7779                    },
 7780                    BracketPair {
 7781                        start: "[".to_string(),
 7782                        end: "]".to_string(),
 7783                        close: false,
 7784                        surround: false,
 7785                        newline: true,
 7786                    },
 7787                    BracketPair {
 7788                        start: "\"".to_string(),
 7789                        end: "\"".to_string(),
 7790                        close: true,
 7791                        surround: true,
 7792                        newline: false,
 7793                    },
 7794                    BracketPair {
 7795                        start: "<".to_string(),
 7796                        end: ">".to_string(),
 7797                        close: false,
 7798                        surround: true,
 7799                        newline: true,
 7800                    },
 7801                ],
 7802                ..Default::default()
 7803            },
 7804            autoclose_before: "})]".to_string(),
 7805            ..Default::default()
 7806        },
 7807        Some(tree_sitter_rust::LANGUAGE.into()),
 7808    ));
 7809
 7810    cx.language_registry().add(language.clone());
 7811    cx.update_buffer(|buffer, cx| {
 7812        buffer.set_language(Some(language), cx);
 7813    });
 7814
 7815    cx.set_state(
 7816        &r#"
 7817            🏀ˇ
 7818            εˇ
 7819            ❤️ˇ
 7820        "#
 7821        .unindent(),
 7822    );
 7823
 7824    // autoclose multiple nested brackets at multiple cursors
 7825    cx.update_editor(|editor, window, cx| {
 7826        editor.handle_input("{", window, cx);
 7827        editor.handle_input("{", window, cx);
 7828        editor.handle_input("{", window, cx);
 7829    });
 7830    cx.assert_editor_state(
 7831        &"
 7832            🏀{{{ˇ}}}
 7833            ε{{{ˇ}}}
 7834            ❤️{{{ˇ}}}
 7835        "
 7836        .unindent(),
 7837    );
 7838
 7839    // insert a different closing bracket
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.handle_input(")", window, cx);
 7842    });
 7843    cx.assert_editor_state(
 7844        &"
 7845            🏀{{{)ˇ}}}
 7846            ε{{{)ˇ}}}
 7847            ❤️{{{)ˇ}}}
 7848        "
 7849        .unindent(),
 7850    );
 7851
 7852    // skip over the auto-closed brackets when typing a closing bracket
 7853    cx.update_editor(|editor, window, cx| {
 7854        editor.move_right(&MoveRight, window, cx);
 7855        editor.handle_input("}", window, cx);
 7856        editor.handle_input("}", window, cx);
 7857        editor.handle_input("}", window, cx);
 7858    });
 7859    cx.assert_editor_state(
 7860        &"
 7861            🏀{{{)}}}}ˇ
 7862            ε{{{)}}}}ˇ
 7863            ❤️{{{)}}}}ˇ
 7864        "
 7865        .unindent(),
 7866    );
 7867
 7868    // autoclose multi-character pairs
 7869    cx.set_state(
 7870        &"
 7871            ˇ
 7872            ˇ
 7873        "
 7874        .unindent(),
 7875    );
 7876    cx.update_editor(|editor, window, cx| {
 7877        editor.handle_input("/", window, cx);
 7878        editor.handle_input("*", window, cx);
 7879    });
 7880    cx.assert_editor_state(
 7881        &"
 7882            /*ˇ */
 7883            /*ˇ */
 7884        "
 7885        .unindent(),
 7886    );
 7887
 7888    // one cursor autocloses a multi-character pair, one cursor
 7889    // does not autoclose.
 7890    cx.set_state(
 7891        &"
 7892 7893            ˇ
 7894        "
 7895        .unindent(),
 7896    );
 7897    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 7898    cx.assert_editor_state(
 7899        &"
 7900            /*ˇ */
 7901 7902        "
 7903        .unindent(),
 7904    );
 7905
 7906    // Don't autoclose if the next character isn't whitespace and isn't
 7907    // listed in the language's "autoclose_before" section.
 7908    cx.set_state("ˇa b");
 7909    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7910    cx.assert_editor_state("{ˇa b");
 7911
 7912    // Don't autoclose if `close` is false for the bracket pair
 7913    cx.set_state("ˇ");
 7914    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 7915    cx.assert_editor_state("");
 7916
 7917    // Surround with brackets if text is selected
 7918    cx.set_state("«aˇ» b");
 7919    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7920    cx.assert_editor_state("{«aˇ»} b");
 7921
 7922    // Autoclose when not immediately after a word character
 7923    cx.set_state("a ˇ");
 7924    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7925    cx.assert_editor_state("a \"ˇ\"");
 7926
 7927    // Autoclose pair where the start and end characters are the same
 7928    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7929    cx.assert_editor_state("a \"\"ˇ");
 7930
 7931    // Don't autoclose when immediately after a word character
 7932    cx.set_state("");
 7933    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7934    cx.assert_editor_state("a\"ˇ");
 7935
 7936    // Do autoclose when after a non-word character
 7937    cx.set_state("");
 7938    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 7939    cx.assert_editor_state("{\"ˇ\"");
 7940
 7941    // Non identical pairs autoclose regardless of preceding character
 7942    cx.set_state("");
 7943    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 7944    cx.assert_editor_state("a{ˇ}");
 7945
 7946    // Don't autoclose pair if autoclose is disabled
 7947    cx.set_state("ˇ");
 7948    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7949    cx.assert_editor_state("");
 7950
 7951    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 7952    cx.set_state("«aˇ» b");
 7953    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 7954    cx.assert_editor_state("<«aˇ»> b");
 7955}
 7956
 7957#[gpui::test]
 7958async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 7959    init_test(cx, |settings| {
 7960        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7961    });
 7962
 7963    let mut cx = EditorTestContext::new(cx).await;
 7964
 7965    let language = Arc::new(Language::new(
 7966        LanguageConfig {
 7967            brackets: BracketPairConfig {
 7968                pairs: vec![
 7969                    BracketPair {
 7970                        start: "{".to_string(),
 7971                        end: "}".to_string(),
 7972                        close: true,
 7973                        surround: true,
 7974                        newline: true,
 7975                    },
 7976                    BracketPair {
 7977                        start: "(".to_string(),
 7978                        end: ")".to_string(),
 7979                        close: true,
 7980                        surround: true,
 7981                        newline: true,
 7982                    },
 7983                    BracketPair {
 7984                        start: "[".to_string(),
 7985                        end: "]".to_string(),
 7986                        close: false,
 7987                        surround: false,
 7988                        newline: true,
 7989                    },
 7990                ],
 7991                ..Default::default()
 7992            },
 7993            autoclose_before: "})]".to_string(),
 7994            ..Default::default()
 7995        },
 7996        Some(tree_sitter_rust::LANGUAGE.into()),
 7997    ));
 7998
 7999    cx.language_registry().add(language.clone());
 8000    cx.update_buffer(|buffer, cx| {
 8001        buffer.set_language(Some(language), cx);
 8002    });
 8003
 8004    cx.set_state(
 8005        &"
 8006            ˇ
 8007            ˇ
 8008            ˇ
 8009        "
 8010        .unindent(),
 8011    );
 8012
 8013    // ensure only matching closing brackets are skipped over
 8014    cx.update_editor(|editor, window, cx| {
 8015        editor.handle_input("}", window, cx);
 8016        editor.move_left(&MoveLeft, window, cx);
 8017        editor.handle_input(")", window, cx);
 8018        editor.move_left(&MoveLeft, window, cx);
 8019    });
 8020    cx.assert_editor_state(
 8021        &"
 8022            ˇ)}
 8023            ˇ)}
 8024            ˇ)}
 8025        "
 8026        .unindent(),
 8027    );
 8028
 8029    // skip-over closing brackets at multiple cursors
 8030    cx.update_editor(|editor, window, cx| {
 8031        editor.handle_input(")", window, cx);
 8032        editor.handle_input("}", window, cx);
 8033    });
 8034    cx.assert_editor_state(
 8035        &"
 8036            )}ˇ
 8037            )}ˇ
 8038            )}ˇ
 8039        "
 8040        .unindent(),
 8041    );
 8042
 8043    // ignore non-close brackets
 8044    cx.update_editor(|editor, window, cx| {
 8045        editor.handle_input("]", window, cx);
 8046        editor.move_left(&MoveLeft, window, cx);
 8047        editor.handle_input("]", window, cx);
 8048    });
 8049    cx.assert_editor_state(
 8050        &"
 8051            )}]ˇ]
 8052            )}]ˇ]
 8053            )}]ˇ]
 8054        "
 8055        .unindent(),
 8056    );
 8057}
 8058
 8059#[gpui::test]
 8060async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8061    init_test(cx, |_| {});
 8062
 8063    let mut cx = EditorTestContext::new(cx).await;
 8064
 8065    let html_language = Arc::new(
 8066        Language::new(
 8067            LanguageConfig {
 8068                name: "HTML".into(),
 8069                brackets: BracketPairConfig {
 8070                    pairs: vec![
 8071                        BracketPair {
 8072                            start: "<".into(),
 8073                            end: ">".into(),
 8074                            close: true,
 8075                            ..Default::default()
 8076                        },
 8077                        BracketPair {
 8078                            start: "{".into(),
 8079                            end: "}".into(),
 8080                            close: true,
 8081                            ..Default::default()
 8082                        },
 8083                        BracketPair {
 8084                            start: "(".into(),
 8085                            end: ")".into(),
 8086                            close: true,
 8087                            ..Default::default()
 8088                        },
 8089                    ],
 8090                    ..Default::default()
 8091                },
 8092                autoclose_before: "})]>".into(),
 8093                ..Default::default()
 8094            },
 8095            Some(tree_sitter_html::LANGUAGE.into()),
 8096        )
 8097        .with_injection_query(
 8098            r#"
 8099            (script_element
 8100                (raw_text) @injection.content
 8101                (#set! injection.language "javascript"))
 8102            "#,
 8103        )
 8104        .unwrap(),
 8105    );
 8106
 8107    let javascript_language = Arc::new(Language::new(
 8108        LanguageConfig {
 8109            name: "JavaScript".into(),
 8110            brackets: BracketPairConfig {
 8111                pairs: vec![
 8112                    BracketPair {
 8113                        start: "/*".into(),
 8114                        end: " */".into(),
 8115                        close: true,
 8116                        ..Default::default()
 8117                    },
 8118                    BracketPair {
 8119                        start: "{".into(),
 8120                        end: "}".into(),
 8121                        close: true,
 8122                        ..Default::default()
 8123                    },
 8124                    BracketPair {
 8125                        start: "(".into(),
 8126                        end: ")".into(),
 8127                        close: true,
 8128                        ..Default::default()
 8129                    },
 8130                ],
 8131                ..Default::default()
 8132            },
 8133            autoclose_before: "})]>".into(),
 8134            ..Default::default()
 8135        },
 8136        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8137    ));
 8138
 8139    cx.language_registry().add(html_language.clone());
 8140    cx.language_registry().add(javascript_language.clone());
 8141
 8142    cx.update_buffer(|buffer, cx| {
 8143        buffer.set_language(Some(html_language), cx);
 8144    });
 8145
 8146    cx.set_state(
 8147        &r#"
 8148            <body>ˇ
 8149                <script>
 8150                    var x = 1;ˇ
 8151                </script>
 8152            </body>ˇ
 8153        "#
 8154        .unindent(),
 8155    );
 8156
 8157    // Precondition: different languages are active at different locations.
 8158    cx.update_editor(|editor, window, cx| {
 8159        let snapshot = editor.snapshot(window, cx);
 8160        let cursors = editor.selections.ranges::<usize>(cx);
 8161        let languages = cursors
 8162            .iter()
 8163            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8164            .collect::<Vec<_>>();
 8165        assert_eq!(
 8166            languages,
 8167            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8168        );
 8169    });
 8170
 8171    // Angle brackets autoclose in HTML, but not JavaScript.
 8172    cx.update_editor(|editor, window, cx| {
 8173        editor.handle_input("<", window, cx);
 8174        editor.handle_input("a", window, cx);
 8175    });
 8176    cx.assert_editor_state(
 8177        &r#"
 8178            <body><aˇ>
 8179                <script>
 8180                    var x = 1;<aˇ
 8181                </script>
 8182            </body><aˇ>
 8183        "#
 8184        .unindent(),
 8185    );
 8186
 8187    // Curly braces and parens autoclose in both HTML and JavaScript.
 8188    cx.update_editor(|editor, window, cx| {
 8189        editor.handle_input(" b=", window, cx);
 8190        editor.handle_input("{", window, cx);
 8191        editor.handle_input("c", window, cx);
 8192        editor.handle_input("(", window, cx);
 8193    });
 8194    cx.assert_editor_state(
 8195        &r#"
 8196            <body><a b={c(ˇ)}>
 8197                <script>
 8198                    var x = 1;<a b={c(ˇ)}
 8199                </script>
 8200            </body><a b={c(ˇ)}>
 8201        "#
 8202        .unindent(),
 8203    );
 8204
 8205    // Brackets that were already autoclosed are skipped.
 8206    cx.update_editor(|editor, window, cx| {
 8207        editor.handle_input(")", window, cx);
 8208        editor.handle_input("d", window, cx);
 8209        editor.handle_input("}", window, cx);
 8210    });
 8211    cx.assert_editor_state(
 8212        &r#"
 8213            <body><a b={c()d}ˇ>
 8214                <script>
 8215                    var x = 1;<a b={c()d}ˇ
 8216                </script>
 8217            </body><a b={c()d}ˇ>
 8218        "#
 8219        .unindent(),
 8220    );
 8221    cx.update_editor(|editor, window, cx| {
 8222        editor.handle_input(">", window, cx);
 8223    });
 8224    cx.assert_editor_state(
 8225        &r#"
 8226            <body><a b={c()d}>ˇ
 8227                <script>
 8228                    var x = 1;<a b={c()d}>ˇ
 8229                </script>
 8230            </body><a b={c()d}>ˇ
 8231        "#
 8232        .unindent(),
 8233    );
 8234
 8235    // Reset
 8236    cx.set_state(
 8237        &r#"
 8238            <body>ˇ
 8239                <script>
 8240                    var x = 1;ˇ
 8241                </script>
 8242            </body>ˇ
 8243        "#
 8244        .unindent(),
 8245    );
 8246
 8247    cx.update_editor(|editor, window, cx| {
 8248        editor.handle_input("<", window, cx);
 8249    });
 8250    cx.assert_editor_state(
 8251        &r#"
 8252            <body><ˇ>
 8253                <script>
 8254                    var x = 1;<ˇ
 8255                </script>
 8256            </body><ˇ>
 8257        "#
 8258        .unindent(),
 8259    );
 8260
 8261    // When backspacing, the closing angle brackets are removed.
 8262    cx.update_editor(|editor, window, cx| {
 8263        editor.backspace(&Backspace, window, cx);
 8264    });
 8265    cx.assert_editor_state(
 8266        &r#"
 8267            <body>ˇ
 8268                <script>
 8269                    var x = 1;ˇ
 8270                </script>
 8271            </body>ˇ
 8272        "#
 8273        .unindent(),
 8274    );
 8275
 8276    // Block comments autoclose in JavaScript, but not HTML.
 8277    cx.update_editor(|editor, window, cx| {
 8278        editor.handle_input("/", window, cx);
 8279        editor.handle_input("*", window, cx);
 8280    });
 8281    cx.assert_editor_state(
 8282        &r#"
 8283            <body>/*ˇ
 8284                <script>
 8285                    var x = 1;/*ˇ */
 8286                </script>
 8287            </body>/*ˇ
 8288        "#
 8289        .unindent(),
 8290    );
 8291}
 8292
 8293#[gpui::test]
 8294async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8295    init_test(cx, |_| {});
 8296
 8297    let mut cx = EditorTestContext::new(cx).await;
 8298
 8299    let rust_language = Arc::new(
 8300        Language::new(
 8301            LanguageConfig {
 8302                name: "Rust".into(),
 8303                brackets: serde_json::from_value(json!([
 8304                    { "start": "{", "end": "}", "close": true, "newline": true },
 8305                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8306                ]))
 8307                .unwrap(),
 8308                autoclose_before: "})]>".into(),
 8309                ..Default::default()
 8310            },
 8311            Some(tree_sitter_rust::LANGUAGE.into()),
 8312        )
 8313        .with_override_query("(string_literal) @string")
 8314        .unwrap(),
 8315    );
 8316
 8317    cx.language_registry().add(rust_language.clone());
 8318    cx.update_buffer(|buffer, cx| {
 8319        buffer.set_language(Some(rust_language), cx);
 8320    });
 8321
 8322    cx.set_state(
 8323        &r#"
 8324            let x = ˇ
 8325        "#
 8326        .unindent(),
 8327    );
 8328
 8329    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8330    cx.update_editor(|editor, window, cx| {
 8331        editor.handle_input("\"", window, cx);
 8332    });
 8333    cx.assert_editor_state(
 8334        &r#"
 8335            let x = "ˇ"
 8336        "#
 8337        .unindent(),
 8338    );
 8339
 8340    // Inserting another quotation mark. The cursor moves across the existing
 8341    // automatically-inserted quotation mark.
 8342    cx.update_editor(|editor, window, cx| {
 8343        editor.handle_input("\"", window, cx);
 8344    });
 8345    cx.assert_editor_state(
 8346        &r#"
 8347            let x = ""ˇ
 8348        "#
 8349        .unindent(),
 8350    );
 8351
 8352    // Reset
 8353    cx.set_state(
 8354        &r#"
 8355            let x = ˇ
 8356        "#
 8357        .unindent(),
 8358    );
 8359
 8360    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8361    cx.update_editor(|editor, window, cx| {
 8362        editor.handle_input("\"", window, cx);
 8363        editor.handle_input(" ", window, cx);
 8364        editor.move_left(&Default::default(), window, cx);
 8365        editor.handle_input("\\", window, cx);
 8366        editor.handle_input("\"", window, cx);
 8367    });
 8368    cx.assert_editor_state(
 8369        &r#"
 8370            let x = "\"ˇ "
 8371        "#
 8372        .unindent(),
 8373    );
 8374
 8375    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8376    // mark. Nothing is inserted.
 8377    cx.update_editor(|editor, window, cx| {
 8378        editor.move_right(&Default::default(), window, cx);
 8379        editor.handle_input("\"", window, cx);
 8380    });
 8381    cx.assert_editor_state(
 8382        &r#"
 8383            let x = "\" "ˇ
 8384        "#
 8385        .unindent(),
 8386    );
 8387}
 8388
 8389#[gpui::test]
 8390async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8391    init_test(cx, |_| {});
 8392
 8393    let language = Arc::new(Language::new(
 8394        LanguageConfig {
 8395            brackets: BracketPairConfig {
 8396                pairs: vec![
 8397                    BracketPair {
 8398                        start: "{".to_string(),
 8399                        end: "}".to_string(),
 8400                        close: true,
 8401                        surround: true,
 8402                        newline: true,
 8403                    },
 8404                    BracketPair {
 8405                        start: "/* ".to_string(),
 8406                        end: "*/".to_string(),
 8407                        close: true,
 8408                        surround: true,
 8409                        ..Default::default()
 8410                    },
 8411                ],
 8412                ..Default::default()
 8413            },
 8414            ..Default::default()
 8415        },
 8416        Some(tree_sitter_rust::LANGUAGE.into()),
 8417    ));
 8418
 8419    let text = r#"
 8420        a
 8421        b
 8422        c
 8423    "#
 8424    .unindent();
 8425
 8426    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8427    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8428    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8429    editor
 8430        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8431        .await;
 8432
 8433    editor.update_in(cx, |editor, window, cx| {
 8434        editor.change_selections(None, window, cx, |s| {
 8435            s.select_display_ranges([
 8436                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8437                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8438                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8439            ])
 8440        });
 8441
 8442        editor.handle_input("{", window, cx);
 8443        editor.handle_input("{", window, cx);
 8444        editor.handle_input("{", window, cx);
 8445        assert_eq!(
 8446            editor.text(cx),
 8447            "
 8448                {{{a}}}
 8449                {{{b}}}
 8450                {{{c}}}
 8451            "
 8452            .unindent()
 8453        );
 8454        assert_eq!(
 8455            editor.selections.display_ranges(cx),
 8456            [
 8457                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8458                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8459                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8460            ]
 8461        );
 8462
 8463        editor.undo(&Undo, window, cx);
 8464        editor.undo(&Undo, window, cx);
 8465        editor.undo(&Undo, window, cx);
 8466        assert_eq!(
 8467            editor.text(cx),
 8468            "
 8469                a
 8470                b
 8471                c
 8472            "
 8473            .unindent()
 8474        );
 8475        assert_eq!(
 8476            editor.selections.display_ranges(cx),
 8477            [
 8478                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8479                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8480                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8481            ]
 8482        );
 8483
 8484        // Ensure inserting the first character of a multi-byte bracket pair
 8485        // doesn't surround the selections with the bracket.
 8486        editor.handle_input("/", window, cx);
 8487        assert_eq!(
 8488            editor.text(cx),
 8489            "
 8490                /
 8491                /
 8492                /
 8493            "
 8494            .unindent()
 8495        );
 8496        assert_eq!(
 8497            editor.selections.display_ranges(cx),
 8498            [
 8499                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8500                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8501                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8502            ]
 8503        );
 8504
 8505        editor.undo(&Undo, window, cx);
 8506        assert_eq!(
 8507            editor.text(cx),
 8508            "
 8509                a
 8510                b
 8511                c
 8512            "
 8513            .unindent()
 8514        );
 8515        assert_eq!(
 8516            editor.selections.display_ranges(cx),
 8517            [
 8518                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8519                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8520                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8521            ]
 8522        );
 8523
 8524        // Ensure inserting the last character of a multi-byte bracket pair
 8525        // doesn't surround the selections with the bracket.
 8526        editor.handle_input("*", window, cx);
 8527        assert_eq!(
 8528            editor.text(cx),
 8529            "
 8530                *
 8531                *
 8532                *
 8533            "
 8534            .unindent()
 8535        );
 8536        assert_eq!(
 8537            editor.selections.display_ranges(cx),
 8538            [
 8539                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8540                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8541                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8542            ]
 8543        );
 8544    });
 8545}
 8546
 8547#[gpui::test]
 8548async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8549    init_test(cx, |_| {});
 8550
 8551    let language = Arc::new(Language::new(
 8552        LanguageConfig {
 8553            brackets: BracketPairConfig {
 8554                pairs: vec![BracketPair {
 8555                    start: "{".to_string(),
 8556                    end: "}".to_string(),
 8557                    close: true,
 8558                    surround: true,
 8559                    newline: true,
 8560                }],
 8561                ..Default::default()
 8562            },
 8563            autoclose_before: "}".to_string(),
 8564            ..Default::default()
 8565        },
 8566        Some(tree_sitter_rust::LANGUAGE.into()),
 8567    ));
 8568
 8569    let text = r#"
 8570        a
 8571        b
 8572        c
 8573    "#
 8574    .unindent();
 8575
 8576    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8577    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8578    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8579    editor
 8580        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8581        .await;
 8582
 8583    editor.update_in(cx, |editor, window, cx| {
 8584        editor.change_selections(None, window, cx, |s| {
 8585            s.select_ranges([
 8586                Point::new(0, 1)..Point::new(0, 1),
 8587                Point::new(1, 1)..Point::new(1, 1),
 8588                Point::new(2, 1)..Point::new(2, 1),
 8589            ])
 8590        });
 8591
 8592        editor.handle_input("{", window, cx);
 8593        editor.handle_input("{", window, cx);
 8594        editor.handle_input("_", window, cx);
 8595        assert_eq!(
 8596            editor.text(cx),
 8597            "
 8598                a{{_}}
 8599                b{{_}}
 8600                c{{_}}
 8601            "
 8602            .unindent()
 8603        );
 8604        assert_eq!(
 8605            editor.selections.ranges::<Point>(cx),
 8606            [
 8607                Point::new(0, 4)..Point::new(0, 4),
 8608                Point::new(1, 4)..Point::new(1, 4),
 8609                Point::new(2, 4)..Point::new(2, 4)
 8610            ]
 8611        );
 8612
 8613        editor.backspace(&Default::default(), window, cx);
 8614        editor.backspace(&Default::default(), window, cx);
 8615        assert_eq!(
 8616            editor.text(cx),
 8617            "
 8618                a{}
 8619                b{}
 8620                c{}
 8621            "
 8622            .unindent()
 8623        );
 8624        assert_eq!(
 8625            editor.selections.ranges::<Point>(cx),
 8626            [
 8627                Point::new(0, 2)..Point::new(0, 2),
 8628                Point::new(1, 2)..Point::new(1, 2),
 8629                Point::new(2, 2)..Point::new(2, 2)
 8630            ]
 8631        );
 8632
 8633        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8634        assert_eq!(
 8635            editor.text(cx),
 8636            "
 8637                a
 8638                b
 8639                c
 8640            "
 8641            .unindent()
 8642        );
 8643        assert_eq!(
 8644            editor.selections.ranges::<Point>(cx),
 8645            [
 8646                Point::new(0, 1)..Point::new(0, 1),
 8647                Point::new(1, 1)..Point::new(1, 1),
 8648                Point::new(2, 1)..Point::new(2, 1)
 8649            ]
 8650        );
 8651    });
 8652}
 8653
 8654#[gpui::test]
 8655async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8656    init_test(cx, |settings| {
 8657        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8658    });
 8659
 8660    let mut cx = EditorTestContext::new(cx).await;
 8661
 8662    let language = Arc::new(Language::new(
 8663        LanguageConfig {
 8664            brackets: BracketPairConfig {
 8665                pairs: vec![
 8666                    BracketPair {
 8667                        start: "{".to_string(),
 8668                        end: "}".to_string(),
 8669                        close: true,
 8670                        surround: true,
 8671                        newline: true,
 8672                    },
 8673                    BracketPair {
 8674                        start: "(".to_string(),
 8675                        end: ")".to_string(),
 8676                        close: true,
 8677                        surround: true,
 8678                        newline: true,
 8679                    },
 8680                    BracketPair {
 8681                        start: "[".to_string(),
 8682                        end: "]".to_string(),
 8683                        close: false,
 8684                        surround: true,
 8685                        newline: true,
 8686                    },
 8687                ],
 8688                ..Default::default()
 8689            },
 8690            autoclose_before: "})]".to_string(),
 8691            ..Default::default()
 8692        },
 8693        Some(tree_sitter_rust::LANGUAGE.into()),
 8694    ));
 8695
 8696    cx.language_registry().add(language.clone());
 8697    cx.update_buffer(|buffer, cx| {
 8698        buffer.set_language(Some(language), cx);
 8699    });
 8700
 8701    cx.set_state(
 8702        &"
 8703            {(ˇ)}
 8704            [[ˇ]]
 8705            {(ˇ)}
 8706        "
 8707        .unindent(),
 8708    );
 8709
 8710    cx.update_editor(|editor, window, cx| {
 8711        editor.backspace(&Default::default(), window, cx);
 8712        editor.backspace(&Default::default(), window, cx);
 8713    });
 8714
 8715    cx.assert_editor_state(
 8716        &"
 8717            ˇ
 8718            ˇ]]
 8719            ˇ
 8720        "
 8721        .unindent(),
 8722    );
 8723
 8724    cx.update_editor(|editor, window, cx| {
 8725        editor.handle_input("{", window, cx);
 8726        editor.handle_input("{", window, cx);
 8727        editor.move_right(&MoveRight, window, cx);
 8728        editor.move_right(&MoveRight, window, cx);
 8729        editor.move_left(&MoveLeft, window, cx);
 8730        editor.move_left(&MoveLeft, window, cx);
 8731        editor.backspace(&Default::default(), window, cx);
 8732    });
 8733
 8734    cx.assert_editor_state(
 8735        &"
 8736            {ˇ}
 8737            {ˇ}]]
 8738            {ˇ}
 8739        "
 8740        .unindent(),
 8741    );
 8742
 8743    cx.update_editor(|editor, window, cx| {
 8744        editor.backspace(&Default::default(), window, cx);
 8745    });
 8746
 8747    cx.assert_editor_state(
 8748        &"
 8749            ˇ
 8750            ˇ]]
 8751            ˇ
 8752        "
 8753        .unindent(),
 8754    );
 8755}
 8756
 8757#[gpui::test]
 8758async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8759    init_test(cx, |_| {});
 8760
 8761    let language = Arc::new(Language::new(
 8762        LanguageConfig::default(),
 8763        Some(tree_sitter_rust::LANGUAGE.into()),
 8764    ));
 8765
 8766    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8767    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8768    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8769    editor
 8770        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8771        .await;
 8772
 8773    editor.update_in(cx, |editor, window, cx| {
 8774        editor.set_auto_replace_emoji_shortcode(true);
 8775
 8776        editor.handle_input("Hello ", window, cx);
 8777        editor.handle_input(":wave", window, cx);
 8778        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 8779
 8780        editor.handle_input(":", window, cx);
 8781        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 8782
 8783        editor.handle_input(" :smile", window, cx);
 8784        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 8785
 8786        editor.handle_input(":", window, cx);
 8787        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 8788
 8789        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 8790        editor.handle_input(":wave", window, cx);
 8791        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 8792
 8793        editor.handle_input(":", window, cx);
 8794        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 8795
 8796        editor.handle_input(":1", window, cx);
 8797        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 8798
 8799        editor.handle_input(":", window, cx);
 8800        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 8801
 8802        // Ensure shortcode does not get replaced when it is part of a word
 8803        editor.handle_input(" Test:wave", window, cx);
 8804        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 8805
 8806        editor.handle_input(":", window, cx);
 8807        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 8808
 8809        editor.set_auto_replace_emoji_shortcode(false);
 8810
 8811        // Ensure shortcode does not get replaced when auto replace is off
 8812        editor.handle_input(" :wave", window, cx);
 8813        assert_eq!(
 8814            editor.text(cx),
 8815            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 8816        );
 8817
 8818        editor.handle_input(":", window, cx);
 8819        assert_eq!(
 8820            editor.text(cx),
 8821            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 8822        );
 8823    });
 8824}
 8825
 8826#[gpui::test]
 8827async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 8828    init_test(cx, |_| {});
 8829
 8830    let (text, insertion_ranges) = marked_text_ranges(
 8831        indoc! {"
 8832            ˇ
 8833        "},
 8834        false,
 8835    );
 8836
 8837    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 8838    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8839
 8840    _ = editor.update_in(cx, |editor, window, cx| {
 8841        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 8842
 8843        editor
 8844            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8845            .unwrap();
 8846
 8847        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 8848            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 8849            assert_eq!(editor.text(cx), expected_text);
 8850            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 8851        }
 8852
 8853        assert(
 8854            editor,
 8855            cx,
 8856            indoc! {"
 8857            type «» =•
 8858            "},
 8859        );
 8860
 8861        assert!(editor.context_menu_visible(), "There should be a matches");
 8862    });
 8863}
 8864
 8865#[gpui::test]
 8866async fn test_snippets(cx: &mut TestAppContext) {
 8867    init_test(cx, |_| {});
 8868
 8869    let mut cx = EditorTestContext::new(cx).await;
 8870
 8871    cx.set_state(indoc! {"
 8872        a.ˇ b
 8873        a.ˇ b
 8874        a.ˇ b
 8875    "});
 8876
 8877    cx.update_editor(|editor, window, cx| {
 8878        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 8879        let insertion_ranges = editor
 8880            .selections
 8881            .all(cx)
 8882            .iter()
 8883            .map(|s| s.range().clone())
 8884            .collect::<Vec<_>>();
 8885        editor
 8886            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8887            .unwrap();
 8888    });
 8889
 8890    cx.assert_editor_state(indoc! {"
 8891        a.f(«oneˇ», two, «threeˇ») b
 8892        a.f(«oneˇ», two, «threeˇ») b
 8893        a.f(«oneˇ», two, «threeˇ») b
 8894    "});
 8895
 8896    // Can't move earlier than the first tab stop
 8897    cx.update_editor(|editor, window, cx| {
 8898        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8899    });
 8900    cx.assert_editor_state(indoc! {"
 8901        a.f(«oneˇ», two, «threeˇ») b
 8902        a.f(«oneˇ», two, «threeˇ») b
 8903        a.f(«oneˇ», two, «threeˇ») b
 8904    "});
 8905
 8906    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8907    cx.assert_editor_state(indoc! {"
 8908        a.f(one, «twoˇ», three) b
 8909        a.f(one, «twoˇ», three) b
 8910        a.f(one, «twoˇ», three) b
 8911    "});
 8912
 8913    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 8914    cx.assert_editor_state(indoc! {"
 8915        a.f(«oneˇ», two, «threeˇ») b
 8916        a.f(«oneˇ», two, «threeˇ») b
 8917        a.f(«oneˇ», two, «threeˇ») b
 8918    "});
 8919
 8920    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8921    cx.assert_editor_state(indoc! {"
 8922        a.f(one, «twoˇ», three) b
 8923        a.f(one, «twoˇ», three) b
 8924        a.f(one, «twoˇ», three) b
 8925    "});
 8926    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8927    cx.assert_editor_state(indoc! {"
 8928        a.f(one, two, three)ˇ b
 8929        a.f(one, two, three)ˇ b
 8930        a.f(one, two, three)ˇ b
 8931    "});
 8932
 8933    // As soon as the last tab stop is reached, snippet state is gone
 8934    cx.update_editor(|editor, window, cx| {
 8935        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 8936    });
 8937    cx.assert_editor_state(indoc! {"
 8938        a.f(one, two, three)ˇ b
 8939        a.f(one, two, three)ˇ b
 8940        a.f(one, two, three)ˇ b
 8941    "});
 8942}
 8943
 8944#[gpui::test]
 8945async fn test_snippet_indentation(cx: &mut TestAppContext) {
 8946    init_test(cx, |_| {});
 8947
 8948    let mut cx = EditorTestContext::new(cx).await;
 8949
 8950    cx.update_editor(|editor, window, cx| {
 8951        let snippet = Snippet::parse(indoc! {"
 8952            /*
 8953             * Multiline comment with leading indentation
 8954             *
 8955             * $1
 8956             */
 8957            $0"})
 8958        .unwrap();
 8959        let insertion_ranges = editor
 8960            .selections
 8961            .all(cx)
 8962            .iter()
 8963            .map(|s| s.range().clone())
 8964            .collect::<Vec<_>>();
 8965        editor
 8966            .insert_snippet(&insertion_ranges, snippet, window, cx)
 8967            .unwrap();
 8968    });
 8969
 8970    cx.assert_editor_state(indoc! {"
 8971        /*
 8972         * Multiline comment with leading indentation
 8973         *
 8974         * ˇ
 8975         */
 8976    "});
 8977
 8978    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 8979    cx.assert_editor_state(indoc! {"
 8980        /*
 8981         * Multiline comment with leading indentation
 8982         *
 8983         *•
 8984         */
 8985        ˇ"});
 8986}
 8987
 8988#[gpui::test]
 8989async fn test_document_format_during_save(cx: &mut TestAppContext) {
 8990    init_test(cx, |_| {});
 8991
 8992    let fs = FakeFs::new(cx.executor());
 8993    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8994
 8995    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 8996
 8997    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8998    language_registry.add(rust_lang());
 8999    let mut fake_servers = language_registry.register_fake_lsp(
 9000        "Rust",
 9001        FakeLspAdapter {
 9002            capabilities: lsp::ServerCapabilities {
 9003                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9004                ..Default::default()
 9005            },
 9006            ..Default::default()
 9007        },
 9008    );
 9009
 9010    let buffer = project
 9011        .update(cx, |project, cx| {
 9012            project.open_local_buffer(path!("/file.rs"), cx)
 9013        })
 9014        .await
 9015        .unwrap();
 9016
 9017    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9018    let (editor, cx) = cx.add_window_view(|window, cx| {
 9019        build_editor_with_project(project.clone(), buffer, window, cx)
 9020    });
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.set_text("one\ntwo\nthree\n", window, cx)
 9023    });
 9024    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9025
 9026    cx.executor().start_waiting();
 9027    let fake_server = fake_servers.next().await.unwrap();
 9028
 9029    {
 9030        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9031            move |params, _| async move {
 9032                assert_eq!(
 9033                    params.text_document.uri,
 9034                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9035                );
 9036                assert_eq!(params.options.tab_size, 4);
 9037                Ok(Some(vec![lsp::TextEdit::new(
 9038                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9039                    ", ".to_string(),
 9040                )]))
 9041            },
 9042        );
 9043        let save = editor
 9044            .update_in(cx, |editor, window, cx| {
 9045                editor.save(
 9046                    SaveOptions {
 9047                        format: true,
 9048                        autosave: false,
 9049                    },
 9050                    project.clone(),
 9051                    window,
 9052                    cx,
 9053                )
 9054            })
 9055            .unwrap();
 9056        cx.executor().start_waiting();
 9057        save.await;
 9058
 9059        assert_eq!(
 9060            editor.update(cx, |editor, cx| editor.text(cx)),
 9061            "one, two\nthree\n"
 9062        );
 9063        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9064    }
 9065
 9066    {
 9067        editor.update_in(cx, |editor, window, cx| {
 9068            editor.set_text("one\ntwo\nthree\n", window, cx)
 9069        });
 9070        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9071
 9072        // Ensure we can still save even if formatting hangs.
 9073        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9074            move |params, _| async move {
 9075                assert_eq!(
 9076                    params.text_document.uri,
 9077                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9078                );
 9079                futures::future::pending::<()>().await;
 9080                unreachable!()
 9081            },
 9082        );
 9083        let save = editor
 9084            .update_in(cx, |editor, window, cx| {
 9085                editor.save(
 9086                    SaveOptions {
 9087                        format: true,
 9088                        autosave: false,
 9089                    },
 9090                    project.clone(),
 9091                    window,
 9092                    cx,
 9093                )
 9094            })
 9095            .unwrap();
 9096        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9097        cx.executor().start_waiting();
 9098        save.await;
 9099        assert_eq!(
 9100            editor.update(cx, |editor, cx| editor.text(cx)),
 9101            "one\ntwo\nthree\n"
 9102        );
 9103    }
 9104
 9105    // Set rust language override and assert overridden tabsize is sent to language server
 9106    update_test_language_settings(cx, |settings| {
 9107        settings.languages.insert(
 9108            "Rust".into(),
 9109            LanguageSettingsContent {
 9110                tab_size: NonZeroU32::new(8),
 9111                ..Default::default()
 9112            },
 9113        );
 9114    });
 9115
 9116    {
 9117        editor.update_in(cx, |editor, window, cx| {
 9118            editor.set_text("somehting_new\n", window, cx)
 9119        });
 9120        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9121        let _formatting_request_signal = fake_server
 9122            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9123                assert_eq!(
 9124                    params.text_document.uri,
 9125                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9126                );
 9127                assert_eq!(params.options.tab_size, 8);
 9128                Ok(Some(vec![]))
 9129            });
 9130        let save = editor
 9131            .update_in(cx, |editor, window, cx| {
 9132                editor.save(
 9133                    SaveOptions {
 9134                        format: true,
 9135                        autosave: false,
 9136                    },
 9137                    project.clone(),
 9138                    window,
 9139                    cx,
 9140                )
 9141            })
 9142            .unwrap();
 9143        cx.executor().start_waiting();
 9144        save.await;
 9145    }
 9146}
 9147
 9148#[gpui::test]
 9149async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9150    init_test(cx, |_| {});
 9151
 9152    let cols = 4;
 9153    let rows = 10;
 9154    let sample_text_1 = sample_text(rows, cols, 'a');
 9155    assert_eq!(
 9156        sample_text_1,
 9157        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9158    );
 9159    let sample_text_2 = sample_text(rows, cols, 'l');
 9160    assert_eq!(
 9161        sample_text_2,
 9162        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9163    );
 9164    let sample_text_3 = sample_text(rows, cols, 'v');
 9165    assert_eq!(
 9166        sample_text_3,
 9167        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9168    );
 9169
 9170    let fs = FakeFs::new(cx.executor());
 9171    fs.insert_tree(
 9172        path!("/a"),
 9173        json!({
 9174            "main.rs": sample_text_1,
 9175            "other.rs": sample_text_2,
 9176            "lib.rs": sample_text_3,
 9177        }),
 9178    )
 9179    .await;
 9180
 9181    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9182    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9183    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9184
 9185    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9186    language_registry.add(rust_lang());
 9187    let mut fake_servers = language_registry.register_fake_lsp(
 9188        "Rust",
 9189        FakeLspAdapter {
 9190            capabilities: lsp::ServerCapabilities {
 9191                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9192                ..Default::default()
 9193            },
 9194            ..Default::default()
 9195        },
 9196    );
 9197
 9198    let worktree = project.update(cx, |project, cx| {
 9199        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9200        assert_eq!(worktrees.len(), 1);
 9201        worktrees.pop().unwrap()
 9202    });
 9203    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9204
 9205    let buffer_1 = project
 9206        .update(cx, |project, cx| {
 9207            project.open_buffer((worktree_id, "main.rs"), cx)
 9208        })
 9209        .await
 9210        .unwrap();
 9211    let buffer_2 = project
 9212        .update(cx, |project, cx| {
 9213            project.open_buffer((worktree_id, "other.rs"), cx)
 9214        })
 9215        .await
 9216        .unwrap();
 9217    let buffer_3 = project
 9218        .update(cx, |project, cx| {
 9219            project.open_buffer((worktree_id, "lib.rs"), cx)
 9220        })
 9221        .await
 9222        .unwrap();
 9223
 9224    let multi_buffer = cx.new(|cx| {
 9225        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9226        multi_buffer.push_excerpts(
 9227            buffer_1.clone(),
 9228            [
 9229                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9230                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9231                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9232            ],
 9233            cx,
 9234        );
 9235        multi_buffer.push_excerpts(
 9236            buffer_2.clone(),
 9237            [
 9238                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9239                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9240                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9241            ],
 9242            cx,
 9243        );
 9244        multi_buffer.push_excerpts(
 9245            buffer_3.clone(),
 9246            [
 9247                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9248                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9249                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9250            ],
 9251            cx,
 9252        );
 9253        multi_buffer
 9254    });
 9255    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9256        Editor::new(
 9257            EditorMode::full(),
 9258            multi_buffer,
 9259            Some(project.clone()),
 9260            window,
 9261            cx,
 9262        )
 9263    });
 9264
 9265    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9266        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9267            s.select_ranges(Some(1..2))
 9268        });
 9269        editor.insert("|one|two|three|", window, cx);
 9270    });
 9271    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9272    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9273        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9274            s.select_ranges(Some(60..70))
 9275        });
 9276        editor.insert("|four|five|six|", window, cx);
 9277    });
 9278    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9279
 9280    // First two buffers should be edited, but not the third one.
 9281    assert_eq!(
 9282        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9283        "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}",
 9284    );
 9285    buffer_1.update(cx, |buffer, _| {
 9286        assert!(buffer.is_dirty());
 9287        assert_eq!(
 9288            buffer.text(),
 9289            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9290        )
 9291    });
 9292    buffer_2.update(cx, |buffer, _| {
 9293        assert!(buffer.is_dirty());
 9294        assert_eq!(
 9295            buffer.text(),
 9296            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9297        )
 9298    });
 9299    buffer_3.update(cx, |buffer, _| {
 9300        assert!(!buffer.is_dirty());
 9301        assert_eq!(buffer.text(), sample_text_3,)
 9302    });
 9303    cx.executor().run_until_parked();
 9304
 9305    cx.executor().start_waiting();
 9306    let save = multi_buffer_editor
 9307        .update_in(cx, |editor, window, cx| {
 9308            editor.save(
 9309                SaveOptions {
 9310                    format: true,
 9311                    autosave: false,
 9312                },
 9313                project.clone(),
 9314                window,
 9315                cx,
 9316            )
 9317        })
 9318        .unwrap();
 9319
 9320    let fake_server = fake_servers.next().await.unwrap();
 9321    fake_server
 9322        .server
 9323        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9324            Ok(Some(vec![lsp::TextEdit::new(
 9325                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9326                format!("[{} formatted]", params.text_document.uri),
 9327            )]))
 9328        })
 9329        .detach();
 9330    save.await;
 9331
 9332    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9333    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9334    assert_eq!(
 9335        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9336        uri!(
 9337            "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}"
 9338        ),
 9339    );
 9340    buffer_1.update(cx, |buffer, _| {
 9341        assert!(!buffer.is_dirty());
 9342        assert_eq!(
 9343            buffer.text(),
 9344            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9345        )
 9346    });
 9347    buffer_2.update(cx, |buffer, _| {
 9348        assert!(!buffer.is_dirty());
 9349        assert_eq!(
 9350            buffer.text(),
 9351            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9352        )
 9353    });
 9354    buffer_3.update(cx, |buffer, _| {
 9355        assert!(!buffer.is_dirty());
 9356        assert_eq!(buffer.text(), sample_text_3,)
 9357    });
 9358}
 9359
 9360#[gpui::test]
 9361async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9362    init_test(cx, |_| {});
 9363
 9364    let fs = FakeFs::new(cx.executor());
 9365    fs.insert_tree(
 9366        path!("/dir"),
 9367        json!({
 9368            "file1.rs": "fn main() { println!(\"hello\"); }",
 9369            "file2.rs": "fn test() { println!(\"test\"); }",
 9370            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9371        }),
 9372    )
 9373    .await;
 9374
 9375    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9376    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9377    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9378
 9379    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9380    language_registry.add(rust_lang());
 9381
 9382    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9383    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9384
 9385    // Open three buffers
 9386    let buffer_1 = project
 9387        .update(cx, |project, cx| {
 9388            project.open_buffer((worktree_id, "file1.rs"), cx)
 9389        })
 9390        .await
 9391        .unwrap();
 9392    let buffer_2 = project
 9393        .update(cx, |project, cx| {
 9394            project.open_buffer((worktree_id, "file2.rs"), cx)
 9395        })
 9396        .await
 9397        .unwrap();
 9398    let buffer_3 = project
 9399        .update(cx, |project, cx| {
 9400            project.open_buffer((worktree_id, "file3.rs"), cx)
 9401        })
 9402        .await
 9403        .unwrap();
 9404
 9405    // Create a multi-buffer with all three buffers
 9406    let multi_buffer = cx.new(|cx| {
 9407        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9408        multi_buffer.push_excerpts(
 9409            buffer_1.clone(),
 9410            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9411            cx,
 9412        );
 9413        multi_buffer.push_excerpts(
 9414            buffer_2.clone(),
 9415            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9416            cx,
 9417        );
 9418        multi_buffer.push_excerpts(
 9419            buffer_3.clone(),
 9420            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9421            cx,
 9422        );
 9423        multi_buffer
 9424    });
 9425
 9426    let editor = cx.new_window_entity(|window, cx| {
 9427        Editor::new(
 9428            EditorMode::full(),
 9429            multi_buffer,
 9430            Some(project.clone()),
 9431            window,
 9432            cx,
 9433        )
 9434    });
 9435
 9436    // Edit only the first buffer
 9437    editor.update_in(cx, |editor, window, cx| {
 9438        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 9439            s.select_ranges(Some(10..10))
 9440        });
 9441        editor.insert("// edited", window, cx);
 9442    });
 9443
 9444    // Verify that only buffer 1 is dirty
 9445    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9446    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9447    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9448
 9449    // Get write counts after file creation (files were created with initial content)
 9450    // We expect each file to have been written once during creation
 9451    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9452    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9453    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9454
 9455    // Perform autosave
 9456    let save_task = editor.update_in(cx, |editor, window, cx| {
 9457        editor.save(
 9458            SaveOptions {
 9459                format: true,
 9460                autosave: true,
 9461            },
 9462            project.clone(),
 9463            window,
 9464            cx,
 9465        )
 9466    });
 9467    save_task.await.unwrap();
 9468
 9469    // Only the dirty buffer should have been saved
 9470    assert_eq!(
 9471        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9472        1,
 9473        "Buffer 1 was dirty, so it should have been written once during autosave"
 9474    );
 9475    assert_eq!(
 9476        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9477        0,
 9478        "Buffer 2 was clean, so it should not have been written during autosave"
 9479    );
 9480    assert_eq!(
 9481        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9482        0,
 9483        "Buffer 3 was clean, so it should not have been written during autosave"
 9484    );
 9485
 9486    // Verify buffer states after autosave
 9487    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9488    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9489    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9490
 9491    // Now perform a manual save (format = true)
 9492    let save_task = editor.update_in(cx, |editor, window, cx| {
 9493        editor.save(
 9494            SaveOptions {
 9495                format: true,
 9496                autosave: false,
 9497            },
 9498            project.clone(),
 9499            window,
 9500            cx,
 9501        )
 9502    });
 9503    save_task.await.unwrap();
 9504
 9505    // During manual save, clean buffers don't get written to disk
 9506    // They just get did_save called for language server notifications
 9507    assert_eq!(
 9508        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9509        1,
 9510        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9511    );
 9512    assert_eq!(
 9513        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9514        0,
 9515        "Buffer 2 should not have been written at all"
 9516    );
 9517    assert_eq!(
 9518        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9519        0,
 9520        "Buffer 3 should not have been written at all"
 9521    );
 9522}
 9523
 9524#[gpui::test]
 9525async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9526    init_test(cx, |_| {});
 9527
 9528    let fs = FakeFs::new(cx.executor());
 9529    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9530
 9531    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9532
 9533    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9534    language_registry.add(rust_lang());
 9535    let mut fake_servers = language_registry.register_fake_lsp(
 9536        "Rust",
 9537        FakeLspAdapter {
 9538            capabilities: lsp::ServerCapabilities {
 9539                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9540                ..Default::default()
 9541            },
 9542            ..Default::default()
 9543        },
 9544    );
 9545
 9546    let buffer = project
 9547        .update(cx, |project, cx| {
 9548            project.open_local_buffer(path!("/file.rs"), cx)
 9549        })
 9550        .await
 9551        .unwrap();
 9552
 9553    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9554    let (editor, cx) = cx.add_window_view(|window, cx| {
 9555        build_editor_with_project(project.clone(), buffer, window, cx)
 9556    });
 9557    editor.update_in(cx, |editor, window, cx| {
 9558        editor.set_text("one\ntwo\nthree\n", window, cx)
 9559    });
 9560    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9561
 9562    cx.executor().start_waiting();
 9563    let fake_server = fake_servers.next().await.unwrap();
 9564
 9565    let save = editor
 9566        .update_in(cx, |editor, window, cx| {
 9567            editor.save(
 9568                SaveOptions {
 9569                    format: true,
 9570                    autosave: false,
 9571                },
 9572                project.clone(),
 9573                window,
 9574                cx,
 9575            )
 9576        })
 9577        .unwrap();
 9578    fake_server
 9579        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9580            assert_eq!(
 9581                params.text_document.uri,
 9582                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9583            );
 9584            assert_eq!(params.options.tab_size, 4);
 9585            Ok(Some(vec![lsp::TextEdit::new(
 9586                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9587                ", ".to_string(),
 9588            )]))
 9589        })
 9590        .next()
 9591        .await;
 9592    cx.executor().start_waiting();
 9593    save.await;
 9594    assert_eq!(
 9595        editor.update(cx, |editor, cx| editor.text(cx)),
 9596        "one, two\nthree\n"
 9597    );
 9598    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9599
 9600    editor.update_in(cx, |editor, window, cx| {
 9601        editor.set_text("one\ntwo\nthree\n", window, cx)
 9602    });
 9603    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9604
 9605    // Ensure we can still save even if formatting hangs.
 9606    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9607        move |params, _| async move {
 9608            assert_eq!(
 9609                params.text_document.uri,
 9610                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9611            );
 9612            futures::future::pending::<()>().await;
 9613            unreachable!()
 9614        },
 9615    );
 9616    let save = editor
 9617        .update_in(cx, |editor, window, cx| {
 9618            editor.save(
 9619                SaveOptions {
 9620                    format: true,
 9621                    autosave: false,
 9622                },
 9623                project.clone(),
 9624                window,
 9625                cx,
 9626            )
 9627        })
 9628        .unwrap();
 9629    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9630    cx.executor().start_waiting();
 9631    save.await;
 9632    assert_eq!(
 9633        editor.update(cx, |editor, cx| editor.text(cx)),
 9634        "one\ntwo\nthree\n"
 9635    );
 9636    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9637
 9638    // For non-dirty buffer, no formatting request should be sent
 9639    let save = editor
 9640        .update_in(cx, |editor, window, cx| {
 9641            editor.save(
 9642                SaveOptions {
 9643                    format: false,
 9644                    autosave: false,
 9645                },
 9646                project.clone(),
 9647                window,
 9648                cx,
 9649            )
 9650        })
 9651        .unwrap();
 9652    let _pending_format_request = fake_server
 9653        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9654            panic!("Should not be invoked");
 9655        })
 9656        .next();
 9657    cx.executor().start_waiting();
 9658    save.await;
 9659
 9660    // Set Rust language override and assert overridden tabsize is sent to language server
 9661    update_test_language_settings(cx, |settings| {
 9662        settings.languages.insert(
 9663            "Rust".into(),
 9664            LanguageSettingsContent {
 9665                tab_size: NonZeroU32::new(8),
 9666                ..Default::default()
 9667            },
 9668        );
 9669    });
 9670
 9671    editor.update_in(cx, |editor, window, cx| {
 9672        editor.set_text("somehting_new\n", window, cx)
 9673    });
 9674    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9675    let save = editor
 9676        .update_in(cx, |editor, window, cx| {
 9677            editor.save(
 9678                SaveOptions {
 9679                    format: true,
 9680                    autosave: false,
 9681                },
 9682                project.clone(),
 9683                window,
 9684                cx,
 9685            )
 9686        })
 9687        .unwrap();
 9688    fake_server
 9689        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9690            assert_eq!(
 9691                params.text_document.uri,
 9692                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9693            );
 9694            assert_eq!(params.options.tab_size, 8);
 9695            Ok(Some(Vec::new()))
 9696        })
 9697        .next()
 9698        .await;
 9699    save.await;
 9700}
 9701
 9702#[gpui::test]
 9703async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9704    init_test(cx, |settings| {
 9705        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 9706            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 9707        ))
 9708    });
 9709
 9710    let fs = FakeFs::new(cx.executor());
 9711    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9712
 9713    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9714
 9715    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9716    language_registry.add(Arc::new(Language::new(
 9717        LanguageConfig {
 9718            name: "Rust".into(),
 9719            matcher: LanguageMatcher {
 9720                path_suffixes: vec!["rs".to_string()],
 9721                ..Default::default()
 9722            },
 9723            ..LanguageConfig::default()
 9724        },
 9725        Some(tree_sitter_rust::LANGUAGE.into()),
 9726    )));
 9727    update_test_language_settings(cx, |settings| {
 9728        // Enable Prettier formatting for the same buffer, and ensure
 9729        // LSP is called instead of Prettier.
 9730        settings.defaults.prettier = Some(PrettierSettings {
 9731            allowed: true,
 9732            ..PrettierSettings::default()
 9733        });
 9734    });
 9735    let mut fake_servers = language_registry.register_fake_lsp(
 9736        "Rust",
 9737        FakeLspAdapter {
 9738            capabilities: lsp::ServerCapabilities {
 9739                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9740                ..Default::default()
 9741            },
 9742            ..Default::default()
 9743        },
 9744    );
 9745
 9746    let buffer = project
 9747        .update(cx, |project, cx| {
 9748            project.open_local_buffer(path!("/file.rs"), cx)
 9749        })
 9750        .await
 9751        .unwrap();
 9752
 9753    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9754    let (editor, cx) = cx.add_window_view(|window, cx| {
 9755        build_editor_with_project(project.clone(), buffer, window, cx)
 9756    });
 9757    editor.update_in(cx, |editor, window, cx| {
 9758        editor.set_text("one\ntwo\nthree\n", window, cx)
 9759    });
 9760
 9761    cx.executor().start_waiting();
 9762    let fake_server = fake_servers.next().await.unwrap();
 9763
 9764    let format = editor
 9765        .update_in(cx, |editor, window, cx| {
 9766            editor.perform_format(
 9767                project.clone(),
 9768                FormatTrigger::Manual,
 9769                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9770                window,
 9771                cx,
 9772            )
 9773        })
 9774        .unwrap();
 9775    fake_server
 9776        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9777            assert_eq!(
 9778                params.text_document.uri,
 9779                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9780            );
 9781            assert_eq!(params.options.tab_size, 4);
 9782            Ok(Some(vec![lsp::TextEdit::new(
 9783                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9784                ", ".to_string(),
 9785            )]))
 9786        })
 9787        .next()
 9788        .await;
 9789    cx.executor().start_waiting();
 9790    format.await;
 9791    assert_eq!(
 9792        editor.update(cx, |editor, cx| editor.text(cx)),
 9793        "one, two\nthree\n"
 9794    );
 9795
 9796    editor.update_in(cx, |editor, window, cx| {
 9797        editor.set_text("one\ntwo\nthree\n", window, cx)
 9798    });
 9799    // Ensure we don't lock if formatting hangs.
 9800    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9801        move |params, _| async move {
 9802            assert_eq!(
 9803                params.text_document.uri,
 9804                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9805            );
 9806            futures::future::pending::<()>().await;
 9807            unreachable!()
 9808        },
 9809    );
 9810    let format = editor
 9811        .update_in(cx, |editor, window, cx| {
 9812            editor.perform_format(
 9813                project,
 9814                FormatTrigger::Manual,
 9815                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9816                window,
 9817                cx,
 9818            )
 9819        })
 9820        .unwrap();
 9821    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9822    cx.executor().start_waiting();
 9823    format.await;
 9824    assert_eq!(
 9825        editor.update(cx, |editor, cx| editor.text(cx)),
 9826        "one\ntwo\nthree\n"
 9827    );
 9828}
 9829
 9830#[gpui::test]
 9831async fn test_multiple_formatters(cx: &mut TestAppContext) {
 9832    init_test(cx, |settings| {
 9833        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
 9834        settings.defaults.formatter =
 9835            Some(language_settings::SelectedFormatter::List(FormatterList(
 9836                vec![
 9837                    Formatter::LanguageServer { name: None },
 9838                    Formatter::CodeActions(
 9839                        [
 9840                            ("code-action-1".into(), true),
 9841                            ("code-action-2".into(), true),
 9842                        ]
 9843                        .into_iter()
 9844                        .collect(),
 9845                    ),
 9846                ]
 9847                .into(),
 9848            )))
 9849    });
 9850
 9851    let fs = FakeFs::new(cx.executor());
 9852    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
 9853        .await;
 9854
 9855    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9856    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9857    language_registry.add(rust_lang());
 9858
 9859    let mut fake_servers = language_registry.register_fake_lsp(
 9860        "Rust",
 9861        FakeLspAdapter {
 9862            capabilities: lsp::ServerCapabilities {
 9863                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9864                execute_command_provider: Some(lsp::ExecuteCommandOptions {
 9865                    commands: vec!["the-command-for-code-action-1".into()],
 9866                    ..Default::default()
 9867                }),
 9868                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 9869                ..Default::default()
 9870            },
 9871            ..Default::default()
 9872        },
 9873    );
 9874
 9875    let buffer = project
 9876        .update(cx, |project, cx| {
 9877            project.open_local_buffer(path!("/file.rs"), cx)
 9878        })
 9879        .await
 9880        .unwrap();
 9881
 9882    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9883    let (editor, cx) = cx.add_window_view(|window, cx| {
 9884        build_editor_with_project(project.clone(), buffer, window, cx)
 9885    });
 9886
 9887    cx.executor().start_waiting();
 9888
 9889    let fake_server = fake_servers.next().await.unwrap();
 9890    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9891        move |_params, _| async move {
 9892            Ok(Some(vec![lsp::TextEdit::new(
 9893                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9894                "applied-formatting\n".to_string(),
 9895            )]))
 9896        },
 9897    );
 9898    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
 9899        move |params, _| async move {
 9900            assert_eq!(
 9901                params.context.only,
 9902                Some(vec!["code-action-1".into(), "code-action-2".into()])
 9903            );
 9904            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
 9905            Ok(Some(vec![
 9906                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9907                    kind: Some("code-action-1".into()),
 9908                    edit: Some(lsp::WorkspaceEdit::new(
 9909                        [(
 9910                            uri.clone(),
 9911                            vec![lsp::TextEdit::new(
 9912                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9913                                "applied-code-action-1-edit\n".to_string(),
 9914                            )],
 9915                        )]
 9916                        .into_iter()
 9917                        .collect(),
 9918                    )),
 9919                    command: Some(lsp::Command {
 9920                        command: "the-command-for-code-action-1".into(),
 9921                        ..Default::default()
 9922                    }),
 9923                    ..Default::default()
 9924                }),
 9925                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
 9926                    kind: Some("code-action-2".into()),
 9927                    edit: Some(lsp::WorkspaceEdit::new(
 9928                        [(
 9929                            uri.clone(),
 9930                            vec![lsp::TextEdit::new(
 9931                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9932                                "applied-code-action-2-edit\n".to_string(),
 9933                            )],
 9934                        )]
 9935                        .into_iter()
 9936                        .collect(),
 9937                    )),
 9938                    ..Default::default()
 9939                }),
 9940            ]))
 9941        },
 9942    );
 9943
 9944    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
 9945        move |params, _| async move { Ok(params) }
 9946    });
 9947
 9948    let command_lock = Arc::new(futures::lock::Mutex::new(()));
 9949    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
 9950        let fake = fake_server.clone();
 9951        let lock = command_lock.clone();
 9952        move |params, _| {
 9953            assert_eq!(params.command, "the-command-for-code-action-1");
 9954            let fake = fake.clone();
 9955            let lock = lock.clone();
 9956            async move {
 9957                lock.lock().await;
 9958                fake.server
 9959                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
 9960                        label: None,
 9961                        edit: lsp::WorkspaceEdit {
 9962                            changes: Some(
 9963                                [(
 9964                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
 9965                                    vec![lsp::TextEdit {
 9966                                        range: lsp::Range::new(
 9967                                            lsp::Position::new(0, 0),
 9968                                            lsp::Position::new(0, 0),
 9969                                        ),
 9970                                        new_text: "applied-code-action-1-command\n".into(),
 9971                                    }],
 9972                                )]
 9973                                .into_iter()
 9974                                .collect(),
 9975                            ),
 9976                            ..Default::default()
 9977                        },
 9978                    })
 9979                    .await
 9980                    .into_response()
 9981                    .unwrap();
 9982                Ok(Some(json!(null)))
 9983            }
 9984        }
 9985    });
 9986
 9987    cx.executor().start_waiting();
 9988    editor
 9989        .update_in(cx, |editor, window, cx| {
 9990            editor.perform_format(
 9991                project.clone(),
 9992                FormatTrigger::Manual,
 9993                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
 9994                window,
 9995                cx,
 9996            )
 9997        })
 9998        .unwrap()
 9999        .await;
10000    editor.update(cx, |editor, cx| {
10001        assert_eq!(
10002            editor.text(cx),
10003            r#"
10004                applied-code-action-2-edit
10005                applied-code-action-1-command
10006                applied-code-action-1-edit
10007                applied-formatting
10008                one
10009                two
10010                three
10011            "#
10012            .unindent()
10013        );
10014    });
10015
10016    editor.update_in(cx, |editor, window, cx| {
10017        editor.undo(&Default::default(), window, cx);
10018        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10019    });
10020
10021    // Perform a manual edit while waiting for an LSP command
10022    // that's being run as part of a formatting code action.
10023    let lock_guard = command_lock.lock().await;
10024    let format = editor
10025        .update_in(cx, |editor, window, cx| {
10026            editor.perform_format(
10027                project.clone(),
10028                FormatTrigger::Manual,
10029                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10030                window,
10031                cx,
10032            )
10033        })
10034        .unwrap();
10035    cx.run_until_parked();
10036    editor.update(cx, |editor, cx| {
10037        assert_eq!(
10038            editor.text(cx),
10039            r#"
10040                applied-code-action-1-edit
10041                applied-formatting
10042                one
10043                two
10044                three
10045            "#
10046            .unindent()
10047        );
10048
10049        editor.buffer.update(cx, |buffer, cx| {
10050            let ix = buffer.len(cx);
10051            buffer.edit([(ix..ix, "edited\n")], None, cx);
10052        });
10053    });
10054
10055    // Allow the LSP command to proceed. Because the buffer was edited,
10056    // the second code action will not be run.
10057    drop(lock_guard);
10058    format.await;
10059    editor.update_in(cx, |editor, window, cx| {
10060        assert_eq!(
10061            editor.text(cx),
10062            r#"
10063                applied-code-action-1-command
10064                applied-code-action-1-edit
10065                applied-formatting
10066                one
10067                two
10068                three
10069                edited
10070            "#
10071            .unindent()
10072        );
10073
10074        // The manual edit is undone first, because it is the last thing the user did
10075        // (even though the command completed afterwards).
10076        editor.undo(&Default::default(), window, cx);
10077        assert_eq!(
10078            editor.text(cx),
10079            r#"
10080                applied-code-action-1-command
10081                applied-code-action-1-edit
10082                applied-formatting
10083                one
10084                two
10085                three
10086            "#
10087            .unindent()
10088        );
10089
10090        // All the formatting (including the command, which completed after the manual edit)
10091        // is undone together.
10092        editor.undo(&Default::default(), window, cx);
10093        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10094    });
10095}
10096
10097#[gpui::test]
10098async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10099    init_test(cx, |settings| {
10100        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10101            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
10102        ))
10103    });
10104
10105    let fs = FakeFs::new(cx.executor());
10106    fs.insert_file(path!("/file.ts"), Default::default()).await;
10107
10108    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10109
10110    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10111    language_registry.add(Arc::new(Language::new(
10112        LanguageConfig {
10113            name: "TypeScript".into(),
10114            matcher: LanguageMatcher {
10115                path_suffixes: vec!["ts".to_string()],
10116                ..Default::default()
10117            },
10118            ..LanguageConfig::default()
10119        },
10120        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10121    )));
10122    update_test_language_settings(cx, |settings| {
10123        settings.defaults.prettier = Some(PrettierSettings {
10124            allowed: true,
10125            ..PrettierSettings::default()
10126        });
10127    });
10128    let mut fake_servers = language_registry.register_fake_lsp(
10129        "TypeScript",
10130        FakeLspAdapter {
10131            capabilities: lsp::ServerCapabilities {
10132                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10133                ..Default::default()
10134            },
10135            ..Default::default()
10136        },
10137    );
10138
10139    let buffer = project
10140        .update(cx, |project, cx| {
10141            project.open_local_buffer(path!("/file.ts"), cx)
10142        })
10143        .await
10144        .unwrap();
10145
10146    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10147    let (editor, cx) = cx.add_window_view(|window, cx| {
10148        build_editor_with_project(project.clone(), buffer, window, cx)
10149    });
10150    editor.update_in(cx, |editor, window, cx| {
10151        editor.set_text(
10152            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10153            window,
10154            cx,
10155        )
10156    });
10157
10158    cx.executor().start_waiting();
10159    let fake_server = fake_servers.next().await.unwrap();
10160
10161    let format = editor
10162        .update_in(cx, |editor, window, cx| {
10163            editor.perform_code_action_kind(
10164                project.clone(),
10165                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10166                window,
10167                cx,
10168            )
10169        })
10170        .unwrap();
10171    fake_server
10172        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10173            assert_eq!(
10174                params.text_document.uri,
10175                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10176            );
10177            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10178                lsp::CodeAction {
10179                    title: "Organize Imports".to_string(),
10180                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10181                    edit: Some(lsp::WorkspaceEdit {
10182                        changes: Some(
10183                            [(
10184                                params.text_document.uri.clone(),
10185                                vec![lsp::TextEdit::new(
10186                                    lsp::Range::new(
10187                                        lsp::Position::new(1, 0),
10188                                        lsp::Position::new(2, 0),
10189                                    ),
10190                                    "".to_string(),
10191                                )],
10192                            )]
10193                            .into_iter()
10194                            .collect(),
10195                        ),
10196                        ..Default::default()
10197                    }),
10198                    ..Default::default()
10199                },
10200            )]))
10201        })
10202        .next()
10203        .await;
10204    cx.executor().start_waiting();
10205    format.await;
10206    assert_eq!(
10207        editor.update(cx, |editor, cx| editor.text(cx)),
10208        "import { a } from 'module';\n\nconst x = a;\n"
10209    );
10210
10211    editor.update_in(cx, |editor, window, cx| {
10212        editor.set_text(
10213            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10214            window,
10215            cx,
10216        )
10217    });
10218    // Ensure we don't lock if code action hangs.
10219    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10220        move |params, _| async move {
10221            assert_eq!(
10222                params.text_document.uri,
10223                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10224            );
10225            futures::future::pending::<()>().await;
10226            unreachable!()
10227        },
10228    );
10229    let format = editor
10230        .update_in(cx, |editor, window, cx| {
10231            editor.perform_code_action_kind(
10232                project,
10233                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10234                window,
10235                cx,
10236            )
10237        })
10238        .unwrap();
10239    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10240    cx.executor().start_waiting();
10241    format.await;
10242    assert_eq!(
10243        editor.update(cx, |editor, cx| editor.text(cx)),
10244        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10245    );
10246}
10247
10248#[gpui::test]
10249async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10250    init_test(cx, |_| {});
10251
10252    let mut cx = EditorLspTestContext::new_rust(
10253        lsp::ServerCapabilities {
10254            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10255            ..Default::default()
10256        },
10257        cx,
10258    )
10259    .await;
10260
10261    cx.set_state(indoc! {"
10262        one.twoˇ
10263    "});
10264
10265    // The format request takes a long time. When it completes, it inserts
10266    // a newline and an indent before the `.`
10267    cx.lsp
10268        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10269            let executor = cx.background_executor().clone();
10270            async move {
10271                executor.timer(Duration::from_millis(100)).await;
10272                Ok(Some(vec![lsp::TextEdit {
10273                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10274                    new_text: "\n    ".into(),
10275                }]))
10276            }
10277        });
10278
10279    // Submit a format request.
10280    let format_1 = cx
10281        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10282        .unwrap();
10283    cx.executor().run_until_parked();
10284
10285    // Submit a second format request.
10286    let format_2 = cx
10287        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10288        .unwrap();
10289    cx.executor().run_until_parked();
10290
10291    // Wait for both format requests to complete
10292    cx.executor().advance_clock(Duration::from_millis(200));
10293    cx.executor().start_waiting();
10294    format_1.await.unwrap();
10295    cx.executor().start_waiting();
10296    format_2.await.unwrap();
10297
10298    // The formatting edits only happens once.
10299    cx.assert_editor_state(indoc! {"
10300        one
10301            .twoˇ
10302    "});
10303}
10304
10305#[gpui::test]
10306async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10307    init_test(cx, |settings| {
10308        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10309    });
10310
10311    let mut cx = EditorLspTestContext::new_rust(
10312        lsp::ServerCapabilities {
10313            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10314            ..Default::default()
10315        },
10316        cx,
10317    )
10318    .await;
10319
10320    // Set up a buffer white some trailing whitespace and no trailing newline.
10321    cx.set_state(
10322        &[
10323            "one ",   //
10324            "twoˇ",   //
10325            "three ", //
10326            "four",   //
10327        ]
10328        .join("\n"),
10329    );
10330
10331    // Submit a format request.
10332    let format = cx
10333        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10334        .unwrap();
10335
10336    // Record which buffer changes have been sent to the language server
10337    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10338    cx.lsp
10339        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10340            let buffer_changes = buffer_changes.clone();
10341            move |params, _| {
10342                buffer_changes.lock().extend(
10343                    params
10344                        .content_changes
10345                        .into_iter()
10346                        .map(|e| (e.range.unwrap(), e.text)),
10347                );
10348            }
10349        });
10350
10351    // Handle formatting requests to the language server.
10352    cx.lsp
10353        .set_request_handler::<lsp::request::Formatting, _, _>({
10354            let buffer_changes = buffer_changes.clone();
10355            move |_, _| {
10356                // When formatting is requested, trailing whitespace has already been stripped,
10357                // and the trailing newline has already been added.
10358                assert_eq!(
10359                    &buffer_changes.lock()[1..],
10360                    &[
10361                        (
10362                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10363                            "".into()
10364                        ),
10365                        (
10366                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10367                            "".into()
10368                        ),
10369                        (
10370                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10371                            "\n".into()
10372                        ),
10373                    ]
10374                );
10375
10376                // Insert blank lines between each line of the buffer.
10377                async move {
10378                    Ok(Some(vec![
10379                        lsp::TextEdit {
10380                            range: lsp::Range::new(
10381                                lsp::Position::new(1, 0),
10382                                lsp::Position::new(1, 0),
10383                            ),
10384                            new_text: "\n".into(),
10385                        },
10386                        lsp::TextEdit {
10387                            range: lsp::Range::new(
10388                                lsp::Position::new(2, 0),
10389                                lsp::Position::new(2, 0),
10390                            ),
10391                            new_text: "\n".into(),
10392                        },
10393                    ]))
10394                }
10395            }
10396        });
10397
10398    // After formatting the buffer, the trailing whitespace is stripped,
10399    // a newline is appended, and the edits provided by the language server
10400    // have been applied.
10401    format.await.unwrap();
10402    cx.assert_editor_state(
10403        &[
10404            "one",   //
10405            "",      //
10406            "twoˇ",  //
10407            "",      //
10408            "three", //
10409            "four",  //
10410            "",      //
10411        ]
10412        .join("\n"),
10413    );
10414
10415    // Undoing the formatting undoes the trailing whitespace removal, the
10416    // trailing newline, and the LSP edits.
10417    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10418    cx.assert_editor_state(
10419        &[
10420            "one ",   //
10421            "twoˇ",   //
10422            "three ", //
10423            "four",   //
10424        ]
10425        .join("\n"),
10426    );
10427}
10428
10429#[gpui::test]
10430async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10431    cx: &mut TestAppContext,
10432) {
10433    init_test(cx, |_| {});
10434
10435    cx.update(|cx| {
10436        cx.update_global::<SettingsStore, _>(|settings, cx| {
10437            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10438                settings.auto_signature_help = Some(true);
10439            });
10440        });
10441    });
10442
10443    let mut cx = EditorLspTestContext::new_rust(
10444        lsp::ServerCapabilities {
10445            signature_help_provider: Some(lsp::SignatureHelpOptions {
10446                ..Default::default()
10447            }),
10448            ..Default::default()
10449        },
10450        cx,
10451    )
10452    .await;
10453
10454    let language = Language::new(
10455        LanguageConfig {
10456            name: "Rust".into(),
10457            brackets: BracketPairConfig {
10458                pairs: vec![
10459                    BracketPair {
10460                        start: "{".to_string(),
10461                        end: "}".to_string(),
10462                        close: true,
10463                        surround: true,
10464                        newline: true,
10465                    },
10466                    BracketPair {
10467                        start: "(".to_string(),
10468                        end: ")".to_string(),
10469                        close: true,
10470                        surround: true,
10471                        newline: true,
10472                    },
10473                    BracketPair {
10474                        start: "/*".to_string(),
10475                        end: " */".to_string(),
10476                        close: true,
10477                        surround: true,
10478                        newline: true,
10479                    },
10480                    BracketPair {
10481                        start: "[".to_string(),
10482                        end: "]".to_string(),
10483                        close: false,
10484                        surround: false,
10485                        newline: true,
10486                    },
10487                    BracketPair {
10488                        start: "\"".to_string(),
10489                        end: "\"".to_string(),
10490                        close: true,
10491                        surround: true,
10492                        newline: false,
10493                    },
10494                    BracketPair {
10495                        start: "<".to_string(),
10496                        end: ">".to_string(),
10497                        close: false,
10498                        surround: true,
10499                        newline: true,
10500                    },
10501                ],
10502                ..Default::default()
10503            },
10504            autoclose_before: "})]".to_string(),
10505            ..Default::default()
10506        },
10507        Some(tree_sitter_rust::LANGUAGE.into()),
10508    );
10509    let language = Arc::new(language);
10510
10511    cx.language_registry().add(language.clone());
10512    cx.update_buffer(|buffer, cx| {
10513        buffer.set_language(Some(language), cx);
10514    });
10515
10516    cx.set_state(
10517        &r#"
10518            fn main() {
10519                sampleˇ
10520            }
10521        "#
10522        .unindent(),
10523    );
10524
10525    cx.update_editor(|editor, window, cx| {
10526        editor.handle_input("(", window, cx);
10527    });
10528    cx.assert_editor_state(
10529        &"
10530            fn main() {
10531                sample(ˇ)
10532            }
10533        "
10534        .unindent(),
10535    );
10536
10537    let mocked_response = lsp::SignatureHelp {
10538        signatures: vec![lsp::SignatureInformation {
10539            label: "fn sample(param1: u8, param2: u8)".to_string(),
10540            documentation: None,
10541            parameters: Some(vec![
10542                lsp::ParameterInformation {
10543                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10544                    documentation: None,
10545                },
10546                lsp::ParameterInformation {
10547                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10548                    documentation: None,
10549                },
10550            ]),
10551            active_parameter: None,
10552        }],
10553        active_signature: Some(0),
10554        active_parameter: Some(0),
10555    };
10556    handle_signature_help_request(&mut cx, mocked_response).await;
10557
10558    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10559        .await;
10560
10561    cx.editor(|editor, _, _| {
10562        let signature_help_state = editor.signature_help_state.popover().cloned();
10563        assert_eq!(
10564            signature_help_state.unwrap().label,
10565            "param1: u8, param2: u8"
10566        );
10567    });
10568}
10569
10570#[gpui::test]
10571async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10572    init_test(cx, |_| {});
10573
10574    cx.update(|cx| {
10575        cx.update_global::<SettingsStore, _>(|settings, cx| {
10576            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10577                settings.auto_signature_help = Some(false);
10578                settings.show_signature_help_after_edits = Some(false);
10579            });
10580        });
10581    });
10582
10583    let mut cx = EditorLspTestContext::new_rust(
10584        lsp::ServerCapabilities {
10585            signature_help_provider: Some(lsp::SignatureHelpOptions {
10586                ..Default::default()
10587            }),
10588            ..Default::default()
10589        },
10590        cx,
10591    )
10592    .await;
10593
10594    let language = Language::new(
10595        LanguageConfig {
10596            name: "Rust".into(),
10597            brackets: BracketPairConfig {
10598                pairs: vec![
10599                    BracketPair {
10600                        start: "{".to_string(),
10601                        end: "}".to_string(),
10602                        close: true,
10603                        surround: true,
10604                        newline: true,
10605                    },
10606                    BracketPair {
10607                        start: "(".to_string(),
10608                        end: ")".to_string(),
10609                        close: true,
10610                        surround: true,
10611                        newline: true,
10612                    },
10613                    BracketPair {
10614                        start: "/*".to_string(),
10615                        end: " */".to_string(),
10616                        close: true,
10617                        surround: true,
10618                        newline: true,
10619                    },
10620                    BracketPair {
10621                        start: "[".to_string(),
10622                        end: "]".to_string(),
10623                        close: false,
10624                        surround: false,
10625                        newline: true,
10626                    },
10627                    BracketPair {
10628                        start: "\"".to_string(),
10629                        end: "\"".to_string(),
10630                        close: true,
10631                        surround: true,
10632                        newline: false,
10633                    },
10634                    BracketPair {
10635                        start: "<".to_string(),
10636                        end: ">".to_string(),
10637                        close: false,
10638                        surround: true,
10639                        newline: true,
10640                    },
10641                ],
10642                ..Default::default()
10643            },
10644            autoclose_before: "})]".to_string(),
10645            ..Default::default()
10646        },
10647        Some(tree_sitter_rust::LANGUAGE.into()),
10648    );
10649    let language = Arc::new(language);
10650
10651    cx.language_registry().add(language.clone());
10652    cx.update_buffer(|buffer, cx| {
10653        buffer.set_language(Some(language), cx);
10654    });
10655
10656    // Ensure that signature_help is not called when no signature help is enabled.
10657    cx.set_state(
10658        &r#"
10659            fn main() {
10660                sampleˇ
10661            }
10662        "#
10663        .unindent(),
10664    );
10665    cx.update_editor(|editor, window, cx| {
10666        editor.handle_input("(", window, cx);
10667    });
10668    cx.assert_editor_state(
10669        &"
10670            fn main() {
10671                sample(ˇ)
10672            }
10673        "
10674        .unindent(),
10675    );
10676    cx.editor(|editor, _, _| {
10677        assert!(editor.signature_help_state.task().is_none());
10678    });
10679
10680    let mocked_response = lsp::SignatureHelp {
10681        signatures: vec![lsp::SignatureInformation {
10682            label: "fn sample(param1: u8, param2: u8)".to_string(),
10683            documentation: None,
10684            parameters: Some(vec![
10685                lsp::ParameterInformation {
10686                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10687                    documentation: None,
10688                },
10689                lsp::ParameterInformation {
10690                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10691                    documentation: None,
10692                },
10693            ]),
10694            active_parameter: None,
10695        }],
10696        active_signature: Some(0),
10697        active_parameter: Some(0),
10698    };
10699
10700    // Ensure that signature_help is called when enabled afte edits
10701    cx.update(|_, cx| {
10702        cx.update_global::<SettingsStore, _>(|settings, cx| {
10703            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10704                settings.auto_signature_help = Some(false);
10705                settings.show_signature_help_after_edits = Some(true);
10706            });
10707        });
10708    });
10709    cx.set_state(
10710        &r#"
10711            fn main() {
10712                sampleˇ
10713            }
10714        "#
10715        .unindent(),
10716    );
10717    cx.update_editor(|editor, window, cx| {
10718        editor.handle_input("(", window, cx);
10719    });
10720    cx.assert_editor_state(
10721        &"
10722            fn main() {
10723                sample(ˇ)
10724            }
10725        "
10726        .unindent(),
10727    );
10728    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10729    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10730        .await;
10731    cx.update_editor(|editor, _, _| {
10732        let signature_help_state = editor.signature_help_state.popover().cloned();
10733        assert!(signature_help_state.is_some());
10734        assert_eq!(
10735            signature_help_state.unwrap().label,
10736            "param1: u8, param2: u8"
10737        );
10738        editor.signature_help_state = SignatureHelpState::default();
10739    });
10740
10741    // Ensure that signature_help is called when auto signature help override is enabled
10742    cx.update(|_, cx| {
10743        cx.update_global::<SettingsStore, _>(|settings, cx| {
10744            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10745                settings.auto_signature_help = Some(true);
10746                settings.show_signature_help_after_edits = Some(false);
10747            });
10748        });
10749    });
10750    cx.set_state(
10751        &r#"
10752            fn main() {
10753                sampleˇ
10754            }
10755        "#
10756        .unindent(),
10757    );
10758    cx.update_editor(|editor, window, cx| {
10759        editor.handle_input("(", window, cx);
10760    });
10761    cx.assert_editor_state(
10762        &"
10763            fn main() {
10764                sample(ˇ)
10765            }
10766        "
10767        .unindent(),
10768    );
10769    handle_signature_help_request(&mut cx, mocked_response).await;
10770    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10771        .await;
10772    cx.editor(|editor, _, _| {
10773        let signature_help_state = editor.signature_help_state.popover().cloned();
10774        assert!(signature_help_state.is_some());
10775        assert_eq!(
10776            signature_help_state.unwrap().label,
10777            "param1: u8, param2: u8"
10778        );
10779    });
10780}
10781
10782#[gpui::test]
10783async fn test_signature_help(cx: &mut TestAppContext) {
10784    init_test(cx, |_| {});
10785    cx.update(|cx| {
10786        cx.update_global::<SettingsStore, _>(|settings, cx| {
10787            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10788                settings.auto_signature_help = Some(true);
10789            });
10790        });
10791    });
10792
10793    let mut cx = EditorLspTestContext::new_rust(
10794        lsp::ServerCapabilities {
10795            signature_help_provider: Some(lsp::SignatureHelpOptions {
10796                ..Default::default()
10797            }),
10798            ..Default::default()
10799        },
10800        cx,
10801    )
10802    .await;
10803
10804    // A test that directly calls `show_signature_help`
10805    cx.update_editor(|editor, window, cx| {
10806        editor.show_signature_help(&ShowSignatureHelp, window, cx);
10807    });
10808
10809    let mocked_response = lsp::SignatureHelp {
10810        signatures: vec![lsp::SignatureInformation {
10811            label: "fn sample(param1: u8, param2: u8)".to_string(),
10812            documentation: None,
10813            parameters: Some(vec![
10814                lsp::ParameterInformation {
10815                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10816                    documentation: None,
10817                },
10818                lsp::ParameterInformation {
10819                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10820                    documentation: None,
10821                },
10822            ]),
10823            active_parameter: None,
10824        }],
10825        active_signature: Some(0),
10826        active_parameter: Some(0),
10827    };
10828    handle_signature_help_request(&mut cx, mocked_response).await;
10829
10830    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10831        .await;
10832
10833    cx.editor(|editor, _, _| {
10834        let signature_help_state = editor.signature_help_state.popover().cloned();
10835        assert!(signature_help_state.is_some());
10836        assert_eq!(
10837            signature_help_state.unwrap().label,
10838            "param1: u8, param2: u8"
10839        );
10840    });
10841
10842    // When exiting outside from inside the brackets, `signature_help` is closed.
10843    cx.set_state(indoc! {"
10844        fn main() {
10845            sample(ˇ);
10846        }
10847
10848        fn sample(param1: u8, param2: u8) {}
10849    "});
10850
10851    cx.update_editor(|editor, window, cx| {
10852        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10853    });
10854
10855    let mocked_response = lsp::SignatureHelp {
10856        signatures: Vec::new(),
10857        active_signature: None,
10858        active_parameter: None,
10859    };
10860    handle_signature_help_request(&mut cx, mocked_response).await;
10861
10862    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
10863        .await;
10864
10865    cx.editor(|editor, _, _| {
10866        assert!(!editor.signature_help_state.is_shown());
10867    });
10868
10869    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
10870    cx.set_state(indoc! {"
10871        fn main() {
10872            sample(ˇ);
10873        }
10874
10875        fn sample(param1: u8, param2: u8) {}
10876    "});
10877
10878    let mocked_response = lsp::SignatureHelp {
10879        signatures: vec![lsp::SignatureInformation {
10880            label: "fn sample(param1: u8, param2: u8)".to_string(),
10881            documentation: None,
10882            parameters: Some(vec![
10883                lsp::ParameterInformation {
10884                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10885                    documentation: None,
10886                },
10887                lsp::ParameterInformation {
10888                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10889                    documentation: None,
10890                },
10891            ]),
10892            active_parameter: None,
10893        }],
10894        active_signature: Some(0),
10895        active_parameter: Some(0),
10896    };
10897    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10898    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10899        .await;
10900    cx.editor(|editor, _, _| {
10901        assert!(editor.signature_help_state.is_shown());
10902    });
10903
10904    // Restore the popover with more parameter input
10905    cx.set_state(indoc! {"
10906        fn main() {
10907            sample(param1, param2ˇ);
10908        }
10909
10910        fn sample(param1: u8, param2: u8) {}
10911    "});
10912
10913    let mocked_response = lsp::SignatureHelp {
10914        signatures: vec![lsp::SignatureInformation {
10915            label: "fn sample(param1: u8, param2: u8)".to_string(),
10916            documentation: None,
10917            parameters: Some(vec![
10918                lsp::ParameterInformation {
10919                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10920                    documentation: None,
10921                },
10922                lsp::ParameterInformation {
10923                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10924                    documentation: None,
10925                },
10926            ]),
10927            active_parameter: None,
10928        }],
10929        active_signature: Some(0),
10930        active_parameter: Some(1),
10931    };
10932    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10933    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10934        .await;
10935
10936    // When selecting a range, the popover is gone.
10937    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
10938    cx.update_editor(|editor, window, cx| {
10939        editor.change_selections(None, window, cx, |s| {
10940            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
10941        })
10942    });
10943    cx.assert_editor_state(indoc! {"
10944        fn main() {
10945            sample(param1, «ˇparam2»);
10946        }
10947
10948        fn sample(param1: u8, param2: u8) {}
10949    "});
10950    cx.editor(|editor, _, _| {
10951        assert!(!editor.signature_help_state.is_shown());
10952    });
10953
10954    // When unselecting again, the popover is back if within the brackets.
10955    cx.update_editor(|editor, window, cx| {
10956        editor.change_selections(None, window, cx, |s| {
10957            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10958        })
10959    });
10960    cx.assert_editor_state(indoc! {"
10961        fn main() {
10962            sample(param1, ˇparam2);
10963        }
10964
10965        fn sample(param1: u8, param2: u8) {}
10966    "});
10967    handle_signature_help_request(&mut cx, mocked_response).await;
10968    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10969        .await;
10970    cx.editor(|editor, _, _| {
10971        assert!(editor.signature_help_state.is_shown());
10972    });
10973
10974    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
10975    cx.update_editor(|editor, window, cx| {
10976        editor.change_selections(None, window, cx, |s| {
10977            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
10978            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
10979        })
10980    });
10981    cx.assert_editor_state(indoc! {"
10982        fn main() {
10983            sample(param1, ˇparam2);
10984        }
10985
10986        fn sample(param1: u8, param2: u8) {}
10987    "});
10988
10989    let mocked_response = lsp::SignatureHelp {
10990        signatures: vec![lsp::SignatureInformation {
10991            label: "fn sample(param1: u8, param2: u8)".to_string(),
10992            documentation: None,
10993            parameters: Some(vec![
10994                lsp::ParameterInformation {
10995                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10996                    documentation: None,
10997                },
10998                lsp::ParameterInformation {
10999                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11000                    documentation: None,
11001                },
11002            ]),
11003            active_parameter: None,
11004        }],
11005        active_signature: Some(0),
11006        active_parameter: Some(1),
11007    };
11008    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11009    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11010        .await;
11011    cx.update_editor(|editor, _, cx| {
11012        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11013    });
11014    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11015        .await;
11016    cx.update_editor(|editor, window, cx| {
11017        editor.change_selections(None, window, cx, |s| {
11018            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11019        })
11020    });
11021    cx.assert_editor_state(indoc! {"
11022        fn main() {
11023            sample(param1, «ˇparam2»);
11024        }
11025
11026        fn sample(param1: u8, param2: u8) {}
11027    "});
11028    cx.update_editor(|editor, window, cx| {
11029        editor.change_selections(None, window, cx, |s| {
11030            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11031        })
11032    });
11033    cx.assert_editor_state(indoc! {"
11034        fn main() {
11035            sample(param1, ˇparam2);
11036        }
11037
11038        fn sample(param1: u8, param2: u8) {}
11039    "});
11040    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11041        .await;
11042}
11043
11044#[gpui::test]
11045async fn test_completion_mode(cx: &mut TestAppContext) {
11046    init_test(cx, |_| {});
11047    let mut cx = EditorLspTestContext::new_rust(
11048        lsp::ServerCapabilities {
11049            completion_provider: Some(lsp::CompletionOptions {
11050                resolve_provider: Some(true),
11051                ..Default::default()
11052            }),
11053            ..Default::default()
11054        },
11055        cx,
11056    )
11057    .await;
11058
11059    struct Run {
11060        run_description: &'static str,
11061        initial_state: String,
11062        buffer_marked_text: String,
11063        completion_label: &'static str,
11064        completion_text: &'static str,
11065        expected_with_insert_mode: String,
11066        expected_with_replace_mode: String,
11067        expected_with_replace_subsequence_mode: String,
11068        expected_with_replace_suffix_mode: String,
11069    }
11070
11071    let runs = [
11072        Run {
11073            run_description: "Start of word matches completion text",
11074            initial_state: "before ediˇ after".into(),
11075            buffer_marked_text: "before <edi|> after".into(),
11076            completion_label: "editor",
11077            completion_text: "editor",
11078            expected_with_insert_mode: "before editorˇ after".into(),
11079            expected_with_replace_mode: "before editorˇ after".into(),
11080            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11081            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11082        },
11083        Run {
11084            run_description: "Accept same text at the middle of the word",
11085            initial_state: "before ediˇtor after".into(),
11086            buffer_marked_text: "before <edi|tor> after".into(),
11087            completion_label: "editor",
11088            completion_text: "editor",
11089            expected_with_insert_mode: "before editorˇtor after".into(),
11090            expected_with_replace_mode: "before editorˇ after".into(),
11091            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11092            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11093        },
11094        Run {
11095            run_description: "End of word matches completion text -- cursor at end",
11096            initial_state: "before torˇ after".into(),
11097            buffer_marked_text: "before <tor|> after".into(),
11098            completion_label: "editor",
11099            completion_text: "editor",
11100            expected_with_insert_mode: "before editorˇ after".into(),
11101            expected_with_replace_mode: "before editorˇ after".into(),
11102            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11103            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11104        },
11105        Run {
11106            run_description: "End of word matches completion text -- cursor at start",
11107            initial_state: "before ˇtor after".into(),
11108            buffer_marked_text: "before <|tor> after".into(),
11109            completion_label: "editor",
11110            completion_text: "editor",
11111            expected_with_insert_mode: "before editorˇtor after".into(),
11112            expected_with_replace_mode: "before editorˇ after".into(),
11113            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11114            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11115        },
11116        Run {
11117            run_description: "Prepend text containing whitespace",
11118            initial_state: "pˇfield: bool".into(),
11119            buffer_marked_text: "<p|field>: bool".into(),
11120            completion_label: "pub ",
11121            completion_text: "pub ",
11122            expected_with_insert_mode: "pub ˇfield: bool".into(),
11123            expected_with_replace_mode: "pub ˇ: bool".into(),
11124            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11125            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11126        },
11127        Run {
11128            run_description: "Add element to start of list",
11129            initial_state: "[element_ˇelement_2]".into(),
11130            buffer_marked_text: "[<element_|element_2>]".into(),
11131            completion_label: "element_1",
11132            completion_text: "element_1",
11133            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11134            expected_with_replace_mode: "[element_1ˇ]".into(),
11135            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11136            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11137        },
11138        Run {
11139            run_description: "Add element to start of list -- first and second elements are equal",
11140            initial_state: "[elˇelement]".into(),
11141            buffer_marked_text: "[<el|element>]".into(),
11142            completion_label: "element",
11143            completion_text: "element",
11144            expected_with_insert_mode: "[elementˇelement]".into(),
11145            expected_with_replace_mode: "[elementˇ]".into(),
11146            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11147            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11148        },
11149        Run {
11150            run_description: "Ends with matching suffix",
11151            initial_state: "SubˇError".into(),
11152            buffer_marked_text: "<Sub|Error>".into(),
11153            completion_label: "SubscriptionError",
11154            completion_text: "SubscriptionError",
11155            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11156            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11157            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11158            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11159        },
11160        Run {
11161            run_description: "Suffix is a subsequence -- contiguous",
11162            initial_state: "SubˇErr".into(),
11163            buffer_marked_text: "<Sub|Err>".into(),
11164            completion_label: "SubscriptionError",
11165            completion_text: "SubscriptionError",
11166            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11167            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11168            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11169            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11170        },
11171        Run {
11172            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11173            initial_state: "Suˇscrirr".into(),
11174            buffer_marked_text: "<Su|scrirr>".into(),
11175            completion_label: "SubscriptionError",
11176            completion_text: "SubscriptionError",
11177            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11178            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11179            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11180            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11181        },
11182        Run {
11183            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11184            initial_state: "foo(indˇix)".into(),
11185            buffer_marked_text: "foo(<ind|ix>)".into(),
11186            completion_label: "node_index",
11187            completion_text: "node_index",
11188            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11189            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11190            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11191            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11192        },
11193        Run {
11194            run_description: "Replace range ends before cursor - should extend to cursor",
11195            initial_state: "before editˇo after".into(),
11196            buffer_marked_text: "before <{ed}>it|o after".into(),
11197            completion_label: "editor",
11198            completion_text: "editor",
11199            expected_with_insert_mode: "before editorˇo after".into(),
11200            expected_with_replace_mode: "before editorˇo after".into(),
11201            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11202            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11203        },
11204        Run {
11205            run_description: "Uses label for suffix matching",
11206            initial_state: "before ediˇtor after".into(),
11207            buffer_marked_text: "before <edi|tor> after".into(),
11208            completion_label: "editor",
11209            completion_text: "editor()",
11210            expected_with_insert_mode: "before editor()ˇtor after".into(),
11211            expected_with_replace_mode: "before editor()ˇ after".into(),
11212            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11213            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11214        },
11215        Run {
11216            run_description: "Case insensitive subsequence and suffix matching",
11217            initial_state: "before EDiˇtoR after".into(),
11218            buffer_marked_text: "before <EDi|toR> after".into(),
11219            completion_label: "editor",
11220            completion_text: "editor",
11221            expected_with_insert_mode: "before editorˇtoR after".into(),
11222            expected_with_replace_mode: "before editorˇ after".into(),
11223            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11224            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11225        },
11226    ];
11227
11228    for run in runs {
11229        let run_variations = [
11230            (LspInsertMode::Insert, run.expected_with_insert_mode),
11231            (LspInsertMode::Replace, run.expected_with_replace_mode),
11232            (
11233                LspInsertMode::ReplaceSubsequence,
11234                run.expected_with_replace_subsequence_mode,
11235            ),
11236            (
11237                LspInsertMode::ReplaceSuffix,
11238                run.expected_with_replace_suffix_mode,
11239            ),
11240        ];
11241
11242        for (lsp_insert_mode, expected_text) in run_variations {
11243            eprintln!(
11244                "run = {:?}, mode = {lsp_insert_mode:.?}",
11245                run.run_description,
11246            );
11247
11248            update_test_language_settings(&mut cx, |settings| {
11249                settings.defaults.completions = Some(CompletionSettings {
11250                    lsp_insert_mode,
11251                    words: WordsCompletionMode::Disabled,
11252                    lsp: true,
11253                    lsp_fetch_timeout_ms: 0,
11254                });
11255            });
11256
11257            cx.set_state(&run.initial_state);
11258            cx.update_editor(|editor, window, cx| {
11259                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11260            });
11261
11262            let counter = Arc::new(AtomicUsize::new(0));
11263            handle_completion_request_with_insert_and_replace(
11264                &mut cx,
11265                &run.buffer_marked_text,
11266                vec![(run.completion_label, run.completion_text)],
11267                counter.clone(),
11268            )
11269            .await;
11270            cx.condition(|editor, _| editor.context_menu_visible())
11271                .await;
11272            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11273
11274            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11275                editor
11276                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11277                    .unwrap()
11278            });
11279            cx.assert_editor_state(&expected_text);
11280            handle_resolve_completion_request(&mut cx, None).await;
11281            apply_additional_edits.await.unwrap();
11282        }
11283    }
11284}
11285
11286#[gpui::test]
11287async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11288    init_test(cx, |_| {});
11289    let mut cx = EditorLspTestContext::new_rust(
11290        lsp::ServerCapabilities {
11291            completion_provider: Some(lsp::CompletionOptions {
11292                resolve_provider: Some(true),
11293                ..Default::default()
11294            }),
11295            ..Default::default()
11296        },
11297        cx,
11298    )
11299    .await;
11300
11301    let initial_state = "SubˇError";
11302    let buffer_marked_text = "<Sub|Error>";
11303    let completion_text = "SubscriptionError";
11304    let expected_with_insert_mode = "SubscriptionErrorˇError";
11305    let expected_with_replace_mode = "SubscriptionErrorˇ";
11306
11307    update_test_language_settings(&mut cx, |settings| {
11308        settings.defaults.completions = Some(CompletionSettings {
11309            words: WordsCompletionMode::Disabled,
11310            // set the opposite here to ensure that the action is overriding the default behavior
11311            lsp_insert_mode: LspInsertMode::Insert,
11312            lsp: true,
11313            lsp_fetch_timeout_ms: 0,
11314        });
11315    });
11316
11317    cx.set_state(initial_state);
11318    cx.update_editor(|editor, window, cx| {
11319        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11320    });
11321
11322    let counter = Arc::new(AtomicUsize::new(0));
11323    handle_completion_request_with_insert_and_replace(
11324        &mut cx,
11325        &buffer_marked_text,
11326        vec![(completion_text, completion_text)],
11327        counter.clone(),
11328    )
11329    .await;
11330    cx.condition(|editor, _| editor.context_menu_visible())
11331        .await;
11332    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11333
11334    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11335        editor
11336            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11337            .unwrap()
11338    });
11339    cx.assert_editor_state(&expected_with_replace_mode);
11340    handle_resolve_completion_request(&mut cx, None).await;
11341    apply_additional_edits.await.unwrap();
11342
11343    update_test_language_settings(&mut cx, |settings| {
11344        settings.defaults.completions = Some(CompletionSettings {
11345            words: WordsCompletionMode::Disabled,
11346            // set the opposite here to ensure that the action is overriding the default behavior
11347            lsp_insert_mode: LspInsertMode::Replace,
11348            lsp: true,
11349            lsp_fetch_timeout_ms: 0,
11350        });
11351    });
11352
11353    cx.set_state(initial_state);
11354    cx.update_editor(|editor, window, cx| {
11355        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11356    });
11357    handle_completion_request_with_insert_and_replace(
11358        &mut cx,
11359        &buffer_marked_text,
11360        vec![(completion_text, completion_text)],
11361        counter.clone(),
11362    )
11363    .await;
11364    cx.condition(|editor, _| editor.context_menu_visible())
11365        .await;
11366    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11367
11368    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11369        editor
11370            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11371            .unwrap()
11372    });
11373    cx.assert_editor_state(&expected_with_insert_mode);
11374    handle_resolve_completion_request(&mut cx, None).await;
11375    apply_additional_edits.await.unwrap();
11376}
11377
11378#[gpui::test]
11379async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11380    init_test(cx, |_| {});
11381    let mut cx = EditorLspTestContext::new_rust(
11382        lsp::ServerCapabilities {
11383            completion_provider: Some(lsp::CompletionOptions {
11384                resolve_provider: Some(true),
11385                ..Default::default()
11386            }),
11387            ..Default::default()
11388        },
11389        cx,
11390    )
11391    .await;
11392
11393    // scenario: surrounding text matches completion text
11394    let completion_text = "to_offset";
11395    let initial_state = indoc! {"
11396        1. buf.to_offˇsuffix
11397        2. buf.to_offˇsuf
11398        3. buf.to_offˇfix
11399        4. buf.to_offˇ
11400        5. into_offˇensive
11401        6. ˇsuffix
11402        7. let ˇ //
11403        8. aaˇzz
11404        9. buf.to_off«zzzzzˇ»suffix
11405        10. buf.«ˇzzzzz»suffix
11406        11. to_off«ˇzzzzz»
11407
11408        buf.to_offˇsuffix  // newest cursor
11409    "};
11410    let completion_marked_buffer = indoc! {"
11411        1. buf.to_offsuffix
11412        2. buf.to_offsuf
11413        3. buf.to_offfix
11414        4. buf.to_off
11415        5. into_offensive
11416        6. suffix
11417        7. let  //
11418        8. aazz
11419        9. buf.to_offzzzzzsuffix
11420        10. buf.zzzzzsuffix
11421        11. to_offzzzzz
11422
11423        buf.<to_off|suffix>  // newest cursor
11424    "};
11425    let expected = indoc! {"
11426        1. buf.to_offsetˇ
11427        2. buf.to_offsetˇsuf
11428        3. buf.to_offsetˇfix
11429        4. buf.to_offsetˇ
11430        5. into_offsetˇensive
11431        6. to_offsetˇsuffix
11432        7. let to_offsetˇ //
11433        8. aato_offsetˇzz
11434        9. buf.to_offsetˇ
11435        10. buf.to_offsetˇsuffix
11436        11. to_offsetˇ
11437
11438        buf.to_offsetˇ  // newest cursor
11439    "};
11440    cx.set_state(initial_state);
11441    cx.update_editor(|editor, window, cx| {
11442        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11443    });
11444    handle_completion_request_with_insert_and_replace(
11445        &mut cx,
11446        completion_marked_buffer,
11447        vec![(completion_text, completion_text)],
11448        Arc::new(AtomicUsize::new(0)),
11449    )
11450    .await;
11451    cx.condition(|editor, _| editor.context_menu_visible())
11452        .await;
11453    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11454        editor
11455            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11456            .unwrap()
11457    });
11458    cx.assert_editor_state(expected);
11459    handle_resolve_completion_request(&mut cx, None).await;
11460    apply_additional_edits.await.unwrap();
11461
11462    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11463    let completion_text = "foo_and_bar";
11464    let initial_state = indoc! {"
11465        1. ooanbˇ
11466        2. zooanbˇ
11467        3. ooanbˇz
11468        4. zooanbˇz
11469        5. ooanˇ
11470        6. oanbˇ
11471
11472        ooanbˇ
11473    "};
11474    let completion_marked_buffer = indoc! {"
11475        1. ooanb
11476        2. zooanb
11477        3. ooanbz
11478        4. zooanbz
11479        5. ooan
11480        6. oanb
11481
11482        <ooanb|>
11483    "};
11484    let expected = indoc! {"
11485        1. foo_and_barˇ
11486        2. zfoo_and_barˇ
11487        3. foo_and_barˇz
11488        4. zfoo_and_barˇz
11489        5. ooanfoo_and_barˇ
11490        6. oanbfoo_and_barˇ
11491
11492        foo_and_barˇ
11493    "};
11494    cx.set_state(initial_state);
11495    cx.update_editor(|editor, window, cx| {
11496        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11497    });
11498    handle_completion_request_with_insert_and_replace(
11499        &mut cx,
11500        completion_marked_buffer,
11501        vec![(completion_text, completion_text)],
11502        Arc::new(AtomicUsize::new(0)),
11503    )
11504    .await;
11505    cx.condition(|editor, _| editor.context_menu_visible())
11506        .await;
11507    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11508        editor
11509            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11510            .unwrap()
11511    });
11512    cx.assert_editor_state(expected);
11513    handle_resolve_completion_request(&mut cx, None).await;
11514    apply_additional_edits.await.unwrap();
11515
11516    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11517    // (expects the same as if it was inserted at the end)
11518    let completion_text = "foo_and_bar";
11519    let initial_state = indoc! {"
11520        1. ooˇanb
11521        2. zooˇanb
11522        3. ooˇanbz
11523        4. zooˇanbz
11524
11525        ooˇanb
11526    "};
11527    let completion_marked_buffer = indoc! {"
11528        1. ooanb
11529        2. zooanb
11530        3. ooanbz
11531        4. zooanbz
11532
11533        <oo|anb>
11534    "};
11535    let expected = indoc! {"
11536        1. foo_and_barˇ
11537        2. zfoo_and_barˇ
11538        3. foo_and_barˇz
11539        4. zfoo_and_barˇz
11540
11541        foo_and_barˇ
11542    "};
11543    cx.set_state(initial_state);
11544    cx.update_editor(|editor, window, cx| {
11545        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11546    });
11547    handle_completion_request_with_insert_and_replace(
11548        &mut cx,
11549        completion_marked_buffer,
11550        vec![(completion_text, completion_text)],
11551        Arc::new(AtomicUsize::new(0)),
11552    )
11553    .await;
11554    cx.condition(|editor, _| editor.context_menu_visible())
11555        .await;
11556    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11557        editor
11558            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11559            .unwrap()
11560    });
11561    cx.assert_editor_state(expected);
11562    handle_resolve_completion_request(&mut cx, None).await;
11563    apply_additional_edits.await.unwrap();
11564}
11565
11566// This used to crash
11567#[gpui::test]
11568async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11569    init_test(cx, |_| {});
11570
11571    let buffer_text = indoc! {"
11572        fn main() {
11573            10.satu;
11574
11575            //
11576            // separate cursors so they open in different excerpts (manually reproducible)
11577            //
11578
11579            10.satu20;
11580        }
11581    "};
11582    let multibuffer_text_with_selections = indoc! {"
11583        fn main() {
11584            10.satuˇ;
11585
11586            //
11587
11588            //
11589
11590            10.satuˇ20;
11591        }
11592    "};
11593    let expected_multibuffer = indoc! {"
11594        fn main() {
11595            10.saturating_sub()ˇ;
11596
11597            //
11598
11599            //
11600
11601            10.saturating_sub()ˇ;
11602        }
11603    "};
11604
11605    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11606    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11607
11608    let fs = FakeFs::new(cx.executor());
11609    fs.insert_tree(
11610        path!("/a"),
11611        json!({
11612            "main.rs": buffer_text,
11613        }),
11614    )
11615    .await;
11616
11617    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11618    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11619    language_registry.add(rust_lang());
11620    let mut fake_servers = language_registry.register_fake_lsp(
11621        "Rust",
11622        FakeLspAdapter {
11623            capabilities: lsp::ServerCapabilities {
11624                completion_provider: Some(lsp::CompletionOptions {
11625                    resolve_provider: None,
11626                    ..lsp::CompletionOptions::default()
11627                }),
11628                ..lsp::ServerCapabilities::default()
11629            },
11630            ..FakeLspAdapter::default()
11631        },
11632    );
11633    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11634    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11635    let buffer = project
11636        .update(cx, |project, cx| {
11637            project.open_local_buffer(path!("/a/main.rs"), cx)
11638        })
11639        .await
11640        .unwrap();
11641
11642    let multi_buffer = cx.new(|cx| {
11643        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11644        multi_buffer.push_excerpts(
11645            buffer.clone(),
11646            [ExcerptRange::new(0..first_excerpt_end)],
11647            cx,
11648        );
11649        multi_buffer.push_excerpts(
11650            buffer.clone(),
11651            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11652            cx,
11653        );
11654        multi_buffer
11655    });
11656
11657    let editor = workspace
11658        .update(cx, |_, window, cx| {
11659            cx.new(|cx| {
11660                Editor::new(
11661                    EditorMode::Full {
11662                        scale_ui_elements_with_buffer_font_size: false,
11663                        show_active_line_background: false,
11664                        sized_by_content: false,
11665                    },
11666                    multi_buffer.clone(),
11667                    Some(project.clone()),
11668                    window,
11669                    cx,
11670                )
11671            })
11672        })
11673        .unwrap();
11674
11675    let pane = workspace
11676        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11677        .unwrap();
11678    pane.update_in(cx, |pane, window, cx| {
11679        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11680    });
11681
11682    let fake_server = fake_servers.next().await.unwrap();
11683
11684    editor.update_in(cx, |editor, window, cx| {
11685        editor.change_selections(None, window, cx, |s| {
11686            s.select_ranges([
11687                Point::new(1, 11)..Point::new(1, 11),
11688                Point::new(7, 11)..Point::new(7, 11),
11689            ])
11690        });
11691
11692        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11693    });
11694
11695    editor.update_in(cx, |editor, window, cx| {
11696        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11697    });
11698
11699    fake_server
11700        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11701            let completion_item = lsp::CompletionItem {
11702                label: "saturating_sub()".into(),
11703                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11704                    lsp::InsertReplaceEdit {
11705                        new_text: "saturating_sub()".to_owned(),
11706                        insert: lsp::Range::new(
11707                            lsp::Position::new(7, 7),
11708                            lsp::Position::new(7, 11),
11709                        ),
11710                        replace: lsp::Range::new(
11711                            lsp::Position::new(7, 7),
11712                            lsp::Position::new(7, 13),
11713                        ),
11714                    },
11715                )),
11716                ..lsp::CompletionItem::default()
11717            };
11718
11719            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11720        })
11721        .next()
11722        .await
11723        .unwrap();
11724
11725    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11726        .await;
11727
11728    editor
11729        .update_in(cx, |editor, window, cx| {
11730            editor
11731                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11732                .unwrap()
11733        })
11734        .await
11735        .unwrap();
11736
11737    editor.update(cx, |editor, cx| {
11738        assert_text_with_selections(editor, expected_multibuffer, cx);
11739    })
11740}
11741
11742#[gpui::test]
11743async fn test_completion(cx: &mut TestAppContext) {
11744    init_test(cx, |_| {});
11745
11746    let mut cx = EditorLspTestContext::new_rust(
11747        lsp::ServerCapabilities {
11748            completion_provider: Some(lsp::CompletionOptions {
11749                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11750                resolve_provider: Some(true),
11751                ..Default::default()
11752            }),
11753            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11754            ..Default::default()
11755        },
11756        cx,
11757    )
11758    .await;
11759    let counter = Arc::new(AtomicUsize::new(0));
11760
11761    cx.set_state(indoc! {"
11762        oneˇ
11763        two
11764        three
11765    "});
11766    cx.simulate_keystroke(".");
11767    handle_completion_request(
11768        indoc! {"
11769            one.|<>
11770            two
11771            three
11772        "},
11773        vec!["first_completion", "second_completion"],
11774        true,
11775        counter.clone(),
11776        &mut cx,
11777    )
11778    .await;
11779    cx.condition(|editor, _| editor.context_menu_visible())
11780        .await;
11781    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11782
11783    let _handler = handle_signature_help_request(
11784        &mut cx,
11785        lsp::SignatureHelp {
11786            signatures: vec![lsp::SignatureInformation {
11787                label: "test signature".to_string(),
11788                documentation: None,
11789                parameters: Some(vec![lsp::ParameterInformation {
11790                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
11791                    documentation: None,
11792                }]),
11793                active_parameter: None,
11794            }],
11795            active_signature: None,
11796            active_parameter: None,
11797        },
11798    );
11799    cx.update_editor(|editor, window, cx| {
11800        assert!(
11801            !editor.signature_help_state.is_shown(),
11802            "No signature help was called for"
11803        );
11804        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11805    });
11806    cx.run_until_parked();
11807    cx.update_editor(|editor, _, _| {
11808        assert!(
11809            !editor.signature_help_state.is_shown(),
11810            "No signature help should be shown when completions menu is open"
11811        );
11812    });
11813
11814    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11815        editor.context_menu_next(&Default::default(), window, cx);
11816        editor
11817            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11818            .unwrap()
11819    });
11820    cx.assert_editor_state(indoc! {"
11821        one.second_completionˇ
11822        two
11823        three
11824    "});
11825
11826    handle_resolve_completion_request(
11827        &mut cx,
11828        Some(vec![
11829            (
11830                //This overlaps with the primary completion edit which is
11831                //misbehavior from the LSP spec, test that we filter it out
11832                indoc! {"
11833                    one.second_ˇcompletion
11834                    two
11835                    threeˇ
11836                "},
11837                "overlapping additional edit",
11838            ),
11839            (
11840                indoc! {"
11841                    one.second_completion
11842                    two
11843                    threeˇ
11844                "},
11845                "\nadditional edit",
11846            ),
11847        ]),
11848    )
11849    .await;
11850    apply_additional_edits.await.unwrap();
11851    cx.assert_editor_state(indoc! {"
11852        one.second_completionˇ
11853        two
11854        three
11855        additional edit
11856    "});
11857
11858    cx.set_state(indoc! {"
11859        one.second_completion
11860        twoˇ
11861        threeˇ
11862        additional edit
11863    "});
11864    cx.simulate_keystroke(" ");
11865    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11866    cx.simulate_keystroke("s");
11867    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11868
11869    cx.assert_editor_state(indoc! {"
11870        one.second_completion
11871        two sˇ
11872        three sˇ
11873        additional edit
11874    "});
11875    handle_completion_request(
11876        indoc! {"
11877            one.second_completion
11878            two s
11879            three <s|>
11880            additional edit
11881        "},
11882        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11883        true,
11884        counter.clone(),
11885        &mut cx,
11886    )
11887    .await;
11888    cx.condition(|editor, _| editor.context_menu_visible())
11889        .await;
11890    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11891
11892    cx.simulate_keystroke("i");
11893
11894    handle_completion_request(
11895        indoc! {"
11896            one.second_completion
11897            two si
11898            three <si|>
11899            additional edit
11900        "},
11901        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
11902        true,
11903        counter.clone(),
11904        &mut cx,
11905    )
11906    .await;
11907    cx.condition(|editor, _| editor.context_menu_visible())
11908        .await;
11909    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
11910
11911    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11912        editor
11913            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11914            .unwrap()
11915    });
11916    cx.assert_editor_state(indoc! {"
11917        one.second_completion
11918        two sixth_completionˇ
11919        three sixth_completionˇ
11920        additional edit
11921    "});
11922
11923    apply_additional_edits.await.unwrap();
11924
11925    update_test_language_settings(&mut cx, |settings| {
11926        settings.defaults.show_completions_on_input = Some(false);
11927    });
11928    cx.set_state("editorˇ");
11929    cx.simulate_keystroke(".");
11930    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11931    cx.simulate_keystrokes("c l o");
11932    cx.assert_editor_state("editor.cloˇ");
11933    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
11934    cx.update_editor(|editor, window, cx| {
11935        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11936    });
11937    handle_completion_request(
11938        "editor.<clo|>",
11939        vec!["close", "clobber"],
11940        true,
11941        counter.clone(),
11942        &mut cx,
11943    )
11944    .await;
11945    cx.condition(|editor, _| editor.context_menu_visible())
11946        .await;
11947    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
11948
11949    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11950        editor
11951            .confirm_completion(&ConfirmCompletion::default(), window, cx)
11952            .unwrap()
11953    });
11954    cx.assert_editor_state("editor.clobberˇ");
11955    handle_resolve_completion_request(&mut cx, None).await;
11956    apply_additional_edits.await.unwrap();
11957}
11958
11959#[gpui::test]
11960async fn test_completion_reuse(cx: &mut TestAppContext) {
11961    init_test(cx, |_| {});
11962
11963    let mut cx = EditorLspTestContext::new_rust(
11964        lsp::ServerCapabilities {
11965            completion_provider: Some(lsp::CompletionOptions {
11966                trigger_characters: Some(vec![".".to_string()]),
11967                ..Default::default()
11968            }),
11969            ..Default::default()
11970        },
11971        cx,
11972    )
11973    .await;
11974
11975    let counter = Arc::new(AtomicUsize::new(0));
11976    cx.set_state("objˇ");
11977    cx.simulate_keystroke(".");
11978
11979    // Initial completion request returns complete results
11980    let is_incomplete = false;
11981    handle_completion_request(
11982        "obj.|<>",
11983        vec!["a", "ab", "abc"],
11984        is_incomplete,
11985        counter.clone(),
11986        &mut cx,
11987    )
11988    .await;
11989    cx.run_until_parked();
11990    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11991    cx.assert_editor_state("obj.ˇ");
11992    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
11993
11994    // Type "a" - filters existing completions
11995    cx.simulate_keystroke("a");
11996    cx.run_until_parked();
11997    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11998    cx.assert_editor_state("obj.aˇ");
11999    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12000
12001    // Type "b" - filters existing completions
12002    cx.simulate_keystroke("b");
12003    cx.run_until_parked();
12004    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12005    cx.assert_editor_state("obj.abˇ");
12006    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12007
12008    // Type "c" - filters existing completions
12009    cx.simulate_keystroke("c");
12010    cx.run_until_parked();
12011    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12012    cx.assert_editor_state("obj.abcˇ");
12013    check_displayed_completions(vec!["abc"], &mut cx);
12014
12015    // Backspace to delete "c" - filters existing completions
12016    cx.update_editor(|editor, window, cx| {
12017        editor.backspace(&Backspace, window, cx);
12018    });
12019    cx.run_until_parked();
12020    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12021    cx.assert_editor_state("obj.abˇ");
12022    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12023
12024    // Moving cursor to the left dismisses menu.
12025    cx.update_editor(|editor, window, cx| {
12026        editor.move_left(&MoveLeft, window, cx);
12027    });
12028    cx.run_until_parked();
12029    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12030    cx.assert_editor_state("obj.aˇb");
12031    cx.update_editor(|editor, _, _| {
12032        assert_eq!(editor.context_menu_visible(), false);
12033    });
12034
12035    // Type "b" - new request
12036    cx.simulate_keystroke("b");
12037    let is_incomplete = false;
12038    handle_completion_request(
12039        "obj.<ab|>a",
12040        vec!["ab", "abc"],
12041        is_incomplete,
12042        counter.clone(),
12043        &mut cx,
12044    )
12045    .await;
12046    cx.run_until_parked();
12047    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12048    cx.assert_editor_state("obj.abˇb");
12049    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12050
12051    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12052    cx.update_editor(|editor, window, cx| {
12053        editor.backspace(&Backspace, window, cx);
12054    });
12055    let is_incomplete = false;
12056    handle_completion_request(
12057        "obj.<a|>b",
12058        vec!["a", "ab", "abc"],
12059        is_incomplete,
12060        counter.clone(),
12061        &mut cx,
12062    )
12063    .await;
12064    cx.run_until_parked();
12065    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12066    cx.assert_editor_state("obj.aˇb");
12067    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12068
12069    // Backspace to delete "a" - dismisses menu.
12070    cx.update_editor(|editor, window, cx| {
12071        editor.backspace(&Backspace, window, cx);
12072    });
12073    cx.run_until_parked();
12074    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12075    cx.assert_editor_state("obj.ˇb");
12076    cx.update_editor(|editor, _, _| {
12077        assert_eq!(editor.context_menu_visible(), false);
12078    });
12079}
12080
12081#[gpui::test]
12082async fn test_word_completion(cx: &mut TestAppContext) {
12083    let lsp_fetch_timeout_ms = 10;
12084    init_test(cx, |language_settings| {
12085        language_settings.defaults.completions = Some(CompletionSettings {
12086            words: WordsCompletionMode::Fallback,
12087            lsp: true,
12088            lsp_fetch_timeout_ms: 10,
12089            lsp_insert_mode: LspInsertMode::Insert,
12090        });
12091    });
12092
12093    let mut cx = EditorLspTestContext::new_rust(
12094        lsp::ServerCapabilities {
12095            completion_provider: Some(lsp::CompletionOptions {
12096                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12097                ..lsp::CompletionOptions::default()
12098            }),
12099            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12100            ..lsp::ServerCapabilities::default()
12101        },
12102        cx,
12103    )
12104    .await;
12105
12106    let throttle_completions = Arc::new(AtomicBool::new(false));
12107
12108    let lsp_throttle_completions = throttle_completions.clone();
12109    let _completion_requests_handler =
12110        cx.lsp
12111            .server
12112            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12113                let lsp_throttle_completions = lsp_throttle_completions.clone();
12114                let cx = cx.clone();
12115                async move {
12116                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12117                        cx.background_executor()
12118                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12119                            .await;
12120                    }
12121                    Ok(Some(lsp::CompletionResponse::Array(vec![
12122                        lsp::CompletionItem {
12123                            label: "first".into(),
12124                            ..lsp::CompletionItem::default()
12125                        },
12126                        lsp::CompletionItem {
12127                            label: "last".into(),
12128                            ..lsp::CompletionItem::default()
12129                        },
12130                    ])))
12131                }
12132            });
12133
12134    cx.set_state(indoc! {"
12135        oneˇ
12136        two
12137        three
12138    "});
12139    cx.simulate_keystroke(".");
12140    cx.executor().run_until_parked();
12141    cx.condition(|editor, _| editor.context_menu_visible())
12142        .await;
12143    cx.update_editor(|editor, window, cx| {
12144        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12145        {
12146            assert_eq!(
12147                completion_menu_entries(&menu),
12148                &["first", "last"],
12149                "When LSP server is fast to reply, no fallback word completions are used"
12150            );
12151        } else {
12152            panic!("expected completion menu to be open");
12153        }
12154        editor.cancel(&Cancel, window, cx);
12155    });
12156    cx.executor().run_until_parked();
12157    cx.condition(|editor, _| !editor.context_menu_visible())
12158        .await;
12159
12160    throttle_completions.store(true, atomic::Ordering::Release);
12161    cx.simulate_keystroke(".");
12162    cx.executor()
12163        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12164    cx.executor().run_until_parked();
12165    cx.condition(|editor, _| editor.context_menu_visible())
12166        .await;
12167    cx.update_editor(|editor, _, _| {
12168        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12169        {
12170            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12171                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12172        } else {
12173            panic!("expected completion menu to be open");
12174        }
12175    });
12176}
12177
12178#[gpui::test]
12179async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12180    init_test(cx, |language_settings| {
12181        language_settings.defaults.completions = Some(CompletionSettings {
12182            words: WordsCompletionMode::Enabled,
12183            lsp: true,
12184            lsp_fetch_timeout_ms: 0,
12185            lsp_insert_mode: LspInsertMode::Insert,
12186        });
12187    });
12188
12189    let mut cx = EditorLspTestContext::new_rust(
12190        lsp::ServerCapabilities {
12191            completion_provider: Some(lsp::CompletionOptions {
12192                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12193                ..lsp::CompletionOptions::default()
12194            }),
12195            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12196            ..lsp::ServerCapabilities::default()
12197        },
12198        cx,
12199    )
12200    .await;
12201
12202    let _completion_requests_handler =
12203        cx.lsp
12204            .server
12205            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12206                Ok(Some(lsp::CompletionResponse::Array(vec![
12207                    lsp::CompletionItem {
12208                        label: "first".into(),
12209                        ..lsp::CompletionItem::default()
12210                    },
12211                    lsp::CompletionItem {
12212                        label: "last".into(),
12213                        ..lsp::CompletionItem::default()
12214                    },
12215                ])))
12216            });
12217
12218    cx.set_state(indoc! {"ˇ
12219        first
12220        last
12221        second
12222    "});
12223    cx.simulate_keystroke(".");
12224    cx.executor().run_until_parked();
12225    cx.condition(|editor, _| editor.context_menu_visible())
12226        .await;
12227    cx.update_editor(|editor, _, _| {
12228        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12229        {
12230            assert_eq!(
12231                completion_menu_entries(&menu),
12232                &["first", "last", "second"],
12233                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12234            );
12235        } else {
12236            panic!("expected completion menu to be open");
12237        }
12238    });
12239}
12240
12241#[gpui::test]
12242async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12243    init_test(cx, |language_settings| {
12244        language_settings.defaults.completions = Some(CompletionSettings {
12245            words: WordsCompletionMode::Disabled,
12246            lsp: true,
12247            lsp_fetch_timeout_ms: 0,
12248            lsp_insert_mode: LspInsertMode::Insert,
12249        });
12250    });
12251
12252    let mut cx = EditorLspTestContext::new_rust(
12253        lsp::ServerCapabilities {
12254            completion_provider: Some(lsp::CompletionOptions {
12255                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12256                ..lsp::CompletionOptions::default()
12257            }),
12258            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12259            ..lsp::ServerCapabilities::default()
12260        },
12261        cx,
12262    )
12263    .await;
12264
12265    let _completion_requests_handler =
12266        cx.lsp
12267            .server
12268            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12269                panic!("LSP completions should not be queried when dealing with word completions")
12270            });
12271
12272    cx.set_state(indoc! {"ˇ
12273        first
12274        last
12275        second
12276    "});
12277    cx.update_editor(|editor, window, cx| {
12278        editor.show_word_completions(&ShowWordCompletions, window, cx);
12279    });
12280    cx.executor().run_until_parked();
12281    cx.condition(|editor, _| editor.context_menu_visible())
12282        .await;
12283    cx.update_editor(|editor, _, _| {
12284        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12285        {
12286            assert_eq!(
12287                completion_menu_entries(&menu),
12288                &["first", "last", "second"],
12289                "`ShowWordCompletions` action should show word completions"
12290            );
12291        } else {
12292            panic!("expected completion menu to be open");
12293        }
12294    });
12295
12296    cx.simulate_keystroke("l");
12297    cx.executor().run_until_parked();
12298    cx.condition(|editor, _| editor.context_menu_visible())
12299        .await;
12300    cx.update_editor(|editor, _, _| {
12301        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12302        {
12303            assert_eq!(
12304                completion_menu_entries(&menu),
12305                &["last"],
12306                "After showing word completions, further editing should filter them and not query the LSP"
12307            );
12308        } else {
12309            panic!("expected completion menu to be open");
12310        }
12311    });
12312}
12313
12314#[gpui::test]
12315async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12316    init_test(cx, |language_settings| {
12317        language_settings.defaults.completions = Some(CompletionSettings {
12318            words: WordsCompletionMode::Fallback,
12319            lsp: false,
12320            lsp_fetch_timeout_ms: 0,
12321            lsp_insert_mode: LspInsertMode::Insert,
12322        });
12323    });
12324
12325    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12326
12327    cx.set_state(indoc! {"ˇ
12328        0_usize
12329        let
12330        33
12331        4.5f32
12332    "});
12333    cx.update_editor(|editor, window, cx| {
12334        editor.show_completions(&ShowCompletions::default(), window, cx);
12335    });
12336    cx.executor().run_until_parked();
12337    cx.condition(|editor, _| editor.context_menu_visible())
12338        .await;
12339    cx.update_editor(|editor, window, cx| {
12340        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12341        {
12342            assert_eq!(
12343                completion_menu_entries(&menu),
12344                &["let"],
12345                "With no digits in the completion query, no digits should be in the word completions"
12346            );
12347        } else {
12348            panic!("expected completion menu to be open");
12349        }
12350        editor.cancel(&Cancel, window, cx);
12351    });
12352
12353    cx.set_state(indoc! {"12354        0_usize
12355        let
12356        3
12357        33.35f32
12358    "});
12359    cx.update_editor(|editor, window, cx| {
12360        editor.show_completions(&ShowCompletions::default(), window, cx);
12361    });
12362    cx.executor().run_until_parked();
12363    cx.condition(|editor, _| editor.context_menu_visible())
12364        .await;
12365    cx.update_editor(|editor, _, _| {
12366        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12367        {
12368            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12369                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12370        } else {
12371            panic!("expected completion menu to be open");
12372        }
12373    });
12374}
12375
12376fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12377    let position = || lsp::Position {
12378        line: params.text_document_position.position.line,
12379        character: params.text_document_position.position.character,
12380    };
12381    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12382        range: lsp::Range {
12383            start: position(),
12384            end: position(),
12385        },
12386        new_text: text.to_string(),
12387    }))
12388}
12389
12390#[gpui::test]
12391async fn test_multiline_completion(cx: &mut TestAppContext) {
12392    init_test(cx, |_| {});
12393
12394    let fs = FakeFs::new(cx.executor());
12395    fs.insert_tree(
12396        path!("/a"),
12397        json!({
12398            "main.ts": "a",
12399        }),
12400    )
12401    .await;
12402
12403    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12404    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12405    let typescript_language = Arc::new(Language::new(
12406        LanguageConfig {
12407            name: "TypeScript".into(),
12408            matcher: LanguageMatcher {
12409                path_suffixes: vec!["ts".to_string()],
12410                ..LanguageMatcher::default()
12411            },
12412            line_comments: vec!["// ".into()],
12413            ..LanguageConfig::default()
12414        },
12415        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12416    ));
12417    language_registry.add(typescript_language.clone());
12418    let mut fake_servers = language_registry.register_fake_lsp(
12419        "TypeScript",
12420        FakeLspAdapter {
12421            capabilities: lsp::ServerCapabilities {
12422                completion_provider: Some(lsp::CompletionOptions {
12423                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12424                    ..lsp::CompletionOptions::default()
12425                }),
12426                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12427                ..lsp::ServerCapabilities::default()
12428            },
12429            // Emulate vtsls label generation
12430            label_for_completion: Some(Box::new(|item, _| {
12431                let text = if let Some(description) = item
12432                    .label_details
12433                    .as_ref()
12434                    .and_then(|label_details| label_details.description.as_ref())
12435                {
12436                    format!("{} {}", item.label, description)
12437                } else if let Some(detail) = &item.detail {
12438                    format!("{} {}", item.label, detail)
12439                } else {
12440                    item.label.clone()
12441                };
12442                let len = text.len();
12443                Some(language::CodeLabel {
12444                    text,
12445                    runs: Vec::new(),
12446                    filter_range: 0..len,
12447                })
12448            })),
12449            ..FakeLspAdapter::default()
12450        },
12451    );
12452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12453    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12454    let worktree_id = workspace
12455        .update(cx, |workspace, _window, cx| {
12456            workspace.project().update(cx, |project, cx| {
12457                project.worktrees(cx).next().unwrap().read(cx).id()
12458            })
12459        })
12460        .unwrap();
12461    let _buffer = project
12462        .update(cx, |project, cx| {
12463            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12464        })
12465        .await
12466        .unwrap();
12467    let editor = workspace
12468        .update(cx, |workspace, window, cx| {
12469            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12470        })
12471        .unwrap()
12472        .await
12473        .unwrap()
12474        .downcast::<Editor>()
12475        .unwrap();
12476    let fake_server = fake_servers.next().await.unwrap();
12477
12478    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12479    let multiline_label_2 = "a\nb\nc\n";
12480    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12481    let multiline_description = "d\ne\nf\n";
12482    let multiline_detail_2 = "g\nh\ni\n";
12483
12484    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12485        move |params, _| async move {
12486            Ok(Some(lsp::CompletionResponse::Array(vec![
12487                lsp::CompletionItem {
12488                    label: multiline_label.to_string(),
12489                    text_edit: gen_text_edit(&params, "new_text_1"),
12490                    ..lsp::CompletionItem::default()
12491                },
12492                lsp::CompletionItem {
12493                    label: "single line label 1".to_string(),
12494                    detail: Some(multiline_detail.to_string()),
12495                    text_edit: gen_text_edit(&params, "new_text_2"),
12496                    ..lsp::CompletionItem::default()
12497                },
12498                lsp::CompletionItem {
12499                    label: "single line label 2".to_string(),
12500                    label_details: Some(lsp::CompletionItemLabelDetails {
12501                        description: Some(multiline_description.to_string()),
12502                        detail: None,
12503                    }),
12504                    text_edit: gen_text_edit(&params, "new_text_2"),
12505                    ..lsp::CompletionItem::default()
12506                },
12507                lsp::CompletionItem {
12508                    label: multiline_label_2.to_string(),
12509                    detail: Some(multiline_detail_2.to_string()),
12510                    text_edit: gen_text_edit(&params, "new_text_3"),
12511                    ..lsp::CompletionItem::default()
12512                },
12513                lsp::CompletionItem {
12514                    label: "Label with many     spaces and \t but without newlines".to_string(),
12515                    detail: Some(
12516                        "Details with many     spaces and \t but without newlines".to_string(),
12517                    ),
12518                    text_edit: gen_text_edit(&params, "new_text_4"),
12519                    ..lsp::CompletionItem::default()
12520                },
12521            ])))
12522        },
12523    );
12524
12525    editor.update_in(cx, |editor, window, cx| {
12526        cx.focus_self(window);
12527        editor.move_to_end(&MoveToEnd, window, cx);
12528        editor.handle_input(".", window, cx);
12529    });
12530    cx.run_until_parked();
12531    completion_handle.next().await.unwrap();
12532
12533    editor.update(cx, |editor, _| {
12534        assert!(editor.context_menu_visible());
12535        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12536        {
12537            let completion_labels = menu
12538                .completions
12539                .borrow()
12540                .iter()
12541                .map(|c| c.label.text.clone())
12542                .collect::<Vec<_>>();
12543            assert_eq!(
12544                completion_labels,
12545                &[
12546                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12547                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12548                    "single line label 2 d e f ",
12549                    "a b c g h i ",
12550                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12551                ],
12552                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12553            );
12554
12555            for completion in menu
12556                .completions
12557                .borrow()
12558                .iter() {
12559                    assert_eq!(
12560                        completion.label.filter_range,
12561                        0..completion.label.text.len(),
12562                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12563                    );
12564                }
12565        } else {
12566            panic!("expected completion menu to be open");
12567        }
12568    });
12569}
12570
12571#[gpui::test]
12572async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12573    init_test(cx, |_| {});
12574    let mut cx = EditorLspTestContext::new_rust(
12575        lsp::ServerCapabilities {
12576            completion_provider: Some(lsp::CompletionOptions {
12577                trigger_characters: Some(vec![".".to_string()]),
12578                ..Default::default()
12579            }),
12580            ..Default::default()
12581        },
12582        cx,
12583    )
12584    .await;
12585    cx.lsp
12586        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12587            Ok(Some(lsp::CompletionResponse::Array(vec![
12588                lsp::CompletionItem {
12589                    label: "first".into(),
12590                    ..Default::default()
12591                },
12592                lsp::CompletionItem {
12593                    label: "last".into(),
12594                    ..Default::default()
12595                },
12596            ])))
12597        });
12598    cx.set_state("variableˇ");
12599    cx.simulate_keystroke(".");
12600    cx.executor().run_until_parked();
12601
12602    cx.update_editor(|editor, _, _| {
12603        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12604        {
12605            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12606        } else {
12607            panic!("expected completion menu to be open");
12608        }
12609    });
12610
12611    cx.update_editor(|editor, window, cx| {
12612        editor.move_page_down(&MovePageDown::default(), window, cx);
12613        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12614        {
12615            assert!(
12616                menu.selected_item == 1,
12617                "expected PageDown to select the last item from the context menu"
12618            );
12619        } else {
12620            panic!("expected completion menu to stay open after PageDown");
12621        }
12622    });
12623
12624    cx.update_editor(|editor, window, cx| {
12625        editor.move_page_up(&MovePageUp::default(), window, cx);
12626        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12627        {
12628            assert!(
12629                menu.selected_item == 0,
12630                "expected PageUp to select the first item from the context menu"
12631            );
12632        } else {
12633            panic!("expected completion menu to stay open after PageUp");
12634        }
12635    });
12636}
12637
12638#[gpui::test]
12639async fn test_as_is_completions(cx: &mut TestAppContext) {
12640    init_test(cx, |_| {});
12641    let mut cx = EditorLspTestContext::new_rust(
12642        lsp::ServerCapabilities {
12643            completion_provider: Some(lsp::CompletionOptions {
12644                ..Default::default()
12645            }),
12646            ..Default::default()
12647        },
12648        cx,
12649    )
12650    .await;
12651    cx.lsp
12652        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12653            Ok(Some(lsp::CompletionResponse::Array(vec![
12654                lsp::CompletionItem {
12655                    label: "unsafe".into(),
12656                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12657                        range: lsp::Range {
12658                            start: lsp::Position {
12659                                line: 1,
12660                                character: 2,
12661                            },
12662                            end: lsp::Position {
12663                                line: 1,
12664                                character: 3,
12665                            },
12666                        },
12667                        new_text: "unsafe".to_string(),
12668                    })),
12669                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12670                    ..Default::default()
12671                },
12672            ])))
12673        });
12674    cx.set_state("fn a() {}\n");
12675    cx.executor().run_until_parked();
12676    cx.update_editor(|editor, window, cx| {
12677        editor.show_completions(
12678            &ShowCompletions {
12679                trigger: Some("\n".into()),
12680            },
12681            window,
12682            cx,
12683        );
12684    });
12685    cx.executor().run_until_parked();
12686
12687    cx.update_editor(|editor, window, cx| {
12688        editor.confirm_completion(&Default::default(), window, cx)
12689    });
12690    cx.executor().run_until_parked();
12691    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12692}
12693
12694#[gpui::test]
12695async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12696    init_test(cx, |_| {});
12697
12698    let mut cx = EditorLspTestContext::new_rust(
12699        lsp::ServerCapabilities {
12700            completion_provider: Some(lsp::CompletionOptions {
12701                trigger_characters: Some(vec![".".to_string()]),
12702                resolve_provider: Some(true),
12703                ..Default::default()
12704            }),
12705            ..Default::default()
12706        },
12707        cx,
12708    )
12709    .await;
12710
12711    cx.set_state("fn main() { let a = 2ˇ; }");
12712    cx.simulate_keystroke(".");
12713    let completion_item = lsp::CompletionItem {
12714        label: "Some".into(),
12715        kind: Some(lsp::CompletionItemKind::SNIPPET),
12716        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12717        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12718            kind: lsp::MarkupKind::Markdown,
12719            value: "```rust\nSome(2)\n```".to_string(),
12720        })),
12721        deprecated: Some(false),
12722        sort_text: Some("Some".to_string()),
12723        filter_text: Some("Some".to_string()),
12724        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12725        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12726            range: lsp::Range {
12727                start: lsp::Position {
12728                    line: 0,
12729                    character: 22,
12730                },
12731                end: lsp::Position {
12732                    line: 0,
12733                    character: 22,
12734                },
12735            },
12736            new_text: "Some(2)".to_string(),
12737        })),
12738        additional_text_edits: Some(vec![lsp::TextEdit {
12739            range: lsp::Range {
12740                start: lsp::Position {
12741                    line: 0,
12742                    character: 20,
12743                },
12744                end: lsp::Position {
12745                    line: 0,
12746                    character: 22,
12747                },
12748            },
12749            new_text: "".to_string(),
12750        }]),
12751        ..Default::default()
12752    };
12753
12754    let closure_completion_item = completion_item.clone();
12755    let counter = Arc::new(AtomicUsize::new(0));
12756    let counter_clone = counter.clone();
12757    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12758        let task_completion_item = closure_completion_item.clone();
12759        counter_clone.fetch_add(1, atomic::Ordering::Release);
12760        async move {
12761            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12762                is_incomplete: true,
12763                item_defaults: None,
12764                items: vec![task_completion_item],
12765            })))
12766        }
12767    });
12768
12769    cx.condition(|editor, _| editor.context_menu_visible())
12770        .await;
12771    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
12772    assert!(request.next().await.is_some());
12773    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12774
12775    cx.simulate_keystrokes("S o m");
12776    cx.condition(|editor, _| editor.context_menu_visible())
12777        .await;
12778    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
12779    assert!(request.next().await.is_some());
12780    assert!(request.next().await.is_some());
12781    assert!(request.next().await.is_some());
12782    request.close();
12783    assert!(request.next().await.is_none());
12784    assert_eq!(
12785        counter.load(atomic::Ordering::Acquire),
12786        4,
12787        "With the completions menu open, only one LSP request should happen per input"
12788    );
12789}
12790
12791#[gpui::test]
12792async fn test_toggle_comment(cx: &mut TestAppContext) {
12793    init_test(cx, |_| {});
12794    let mut cx = EditorTestContext::new(cx).await;
12795    let language = Arc::new(Language::new(
12796        LanguageConfig {
12797            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12798            ..Default::default()
12799        },
12800        Some(tree_sitter_rust::LANGUAGE.into()),
12801    ));
12802    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12803
12804    // If multiple selections intersect a line, the line is only toggled once.
12805    cx.set_state(indoc! {"
12806        fn a() {
12807            «//b();
12808            ˇ»// «c();
12809            //ˇ»  d();
12810        }
12811    "});
12812
12813    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12814
12815    cx.assert_editor_state(indoc! {"
12816        fn a() {
12817            «b();
12818            c();
12819            ˇ» d();
12820        }
12821    "});
12822
12823    // The comment prefix is inserted at the same column for every line in a
12824    // selection.
12825    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12826
12827    cx.assert_editor_state(indoc! {"
12828        fn a() {
12829            // «b();
12830            // c();
12831            ˇ»//  d();
12832        }
12833    "});
12834
12835    // If a selection ends at the beginning of a line, that line is not toggled.
12836    cx.set_selections_state(indoc! {"
12837        fn a() {
12838            // b();
12839            «// c();
12840        ˇ»    //  d();
12841        }
12842    "});
12843
12844    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12845
12846    cx.assert_editor_state(indoc! {"
12847        fn a() {
12848            // b();
12849            «c();
12850        ˇ»    //  d();
12851        }
12852    "});
12853
12854    // If a selection span a single line and is empty, the line is toggled.
12855    cx.set_state(indoc! {"
12856        fn a() {
12857            a();
12858            b();
12859        ˇ
12860        }
12861    "});
12862
12863    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12864
12865    cx.assert_editor_state(indoc! {"
12866        fn a() {
12867            a();
12868            b();
12869        //•ˇ
12870        }
12871    "});
12872
12873    // If a selection span multiple lines, empty lines are not toggled.
12874    cx.set_state(indoc! {"
12875        fn a() {
12876            «a();
12877
12878            c();ˇ»
12879        }
12880    "});
12881
12882    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12883
12884    cx.assert_editor_state(indoc! {"
12885        fn a() {
12886            // «a();
12887
12888            // c();ˇ»
12889        }
12890    "});
12891
12892    // If a selection includes multiple comment prefixes, all lines are uncommented.
12893    cx.set_state(indoc! {"
12894        fn a() {
12895            «// a();
12896            /// b();
12897            //! c();ˇ»
12898        }
12899    "});
12900
12901    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
12902
12903    cx.assert_editor_state(indoc! {"
12904        fn a() {
12905            «a();
12906            b();
12907            c();ˇ»
12908        }
12909    "});
12910}
12911
12912#[gpui::test]
12913async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
12914    init_test(cx, |_| {});
12915    let mut cx = EditorTestContext::new(cx).await;
12916    let language = Arc::new(Language::new(
12917        LanguageConfig {
12918            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
12919            ..Default::default()
12920        },
12921        Some(tree_sitter_rust::LANGUAGE.into()),
12922    ));
12923    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12924
12925    let toggle_comments = &ToggleComments {
12926        advance_downwards: false,
12927        ignore_indent: true,
12928    };
12929
12930    // If multiple selections intersect a line, the line is only toggled once.
12931    cx.set_state(indoc! {"
12932        fn a() {
12933        //    «b();
12934        //    c();
12935        //    ˇ» d();
12936        }
12937    "});
12938
12939    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12940
12941    cx.assert_editor_state(indoc! {"
12942        fn a() {
12943            «b();
12944            c();
12945            ˇ» d();
12946        }
12947    "});
12948
12949    // The comment prefix is inserted at the beginning of each line
12950    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12951
12952    cx.assert_editor_state(indoc! {"
12953        fn a() {
12954        //    «b();
12955        //    c();
12956        //    ˇ» d();
12957        }
12958    "});
12959
12960    // If a selection ends at the beginning of a line, that line is not toggled.
12961    cx.set_selections_state(indoc! {"
12962        fn a() {
12963        //    b();
12964        //    «c();
12965        ˇ»//     d();
12966        }
12967    "});
12968
12969    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12970
12971    cx.assert_editor_state(indoc! {"
12972        fn a() {
12973        //    b();
12974            «c();
12975        ˇ»//     d();
12976        }
12977    "});
12978
12979    // If a selection span a single line and is empty, the line is toggled.
12980    cx.set_state(indoc! {"
12981        fn a() {
12982            a();
12983            b();
12984        ˇ
12985        }
12986    "});
12987
12988    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
12989
12990    cx.assert_editor_state(indoc! {"
12991        fn a() {
12992            a();
12993            b();
12994        //ˇ
12995        }
12996    "});
12997
12998    // If a selection span multiple lines, empty lines are not toggled.
12999    cx.set_state(indoc! {"
13000        fn a() {
13001            «a();
13002
13003            c();ˇ»
13004        }
13005    "});
13006
13007    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13008
13009    cx.assert_editor_state(indoc! {"
13010        fn a() {
13011        //    «a();
13012
13013        //    c();ˇ»
13014        }
13015    "});
13016
13017    // If a selection includes multiple comment prefixes, all lines are uncommented.
13018    cx.set_state(indoc! {"
13019        fn a() {
13020        //    «a();
13021        ///    b();
13022        //!    c();ˇ»
13023        }
13024    "});
13025
13026    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13027
13028    cx.assert_editor_state(indoc! {"
13029        fn a() {
13030            «a();
13031            b();
13032            c();ˇ»
13033        }
13034    "});
13035}
13036
13037#[gpui::test]
13038async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13039    init_test(cx, |_| {});
13040
13041    let language = Arc::new(Language::new(
13042        LanguageConfig {
13043            line_comments: vec!["// ".into()],
13044            ..Default::default()
13045        },
13046        Some(tree_sitter_rust::LANGUAGE.into()),
13047    ));
13048
13049    let mut cx = EditorTestContext::new(cx).await;
13050
13051    cx.language_registry().add(language.clone());
13052    cx.update_buffer(|buffer, cx| {
13053        buffer.set_language(Some(language), cx);
13054    });
13055
13056    let toggle_comments = &ToggleComments {
13057        advance_downwards: true,
13058        ignore_indent: false,
13059    };
13060
13061    // Single cursor on one line -> advance
13062    // Cursor moves horizontally 3 characters as well on non-blank line
13063    cx.set_state(indoc!(
13064        "fn a() {
13065             ˇdog();
13066             cat();
13067        }"
13068    ));
13069    cx.update_editor(|editor, window, cx| {
13070        editor.toggle_comments(toggle_comments, window, cx);
13071    });
13072    cx.assert_editor_state(indoc!(
13073        "fn a() {
13074             // dog();
13075             catˇ();
13076        }"
13077    ));
13078
13079    // Single selection on one line -> don't advance
13080    cx.set_state(indoc!(
13081        "fn a() {
13082             «dog()ˇ»;
13083             cat();
13084        }"
13085    ));
13086    cx.update_editor(|editor, window, cx| {
13087        editor.toggle_comments(toggle_comments, window, cx);
13088    });
13089    cx.assert_editor_state(indoc!(
13090        "fn a() {
13091             // «dog()ˇ»;
13092             cat();
13093        }"
13094    ));
13095
13096    // Multiple cursors on one line -> advance
13097    cx.set_state(indoc!(
13098        "fn a() {
13099             ˇdˇog();
13100             cat();
13101        }"
13102    ));
13103    cx.update_editor(|editor, window, cx| {
13104        editor.toggle_comments(toggle_comments, window, cx);
13105    });
13106    cx.assert_editor_state(indoc!(
13107        "fn a() {
13108             // dog();
13109             catˇ(ˇ);
13110        }"
13111    ));
13112
13113    // Multiple cursors on one line, with selection -> don't advance
13114    cx.set_state(indoc!(
13115        "fn a() {
13116             ˇdˇog«()ˇ»;
13117             cat();
13118        }"
13119    ));
13120    cx.update_editor(|editor, window, cx| {
13121        editor.toggle_comments(toggle_comments, window, cx);
13122    });
13123    cx.assert_editor_state(indoc!(
13124        "fn a() {
13125             // ˇdˇog«()ˇ»;
13126             cat();
13127        }"
13128    ));
13129
13130    // Single cursor on one line -> advance
13131    // Cursor moves to column 0 on blank line
13132    cx.set_state(indoc!(
13133        "fn a() {
13134             ˇdog();
13135
13136             cat();
13137        }"
13138    ));
13139    cx.update_editor(|editor, window, cx| {
13140        editor.toggle_comments(toggle_comments, window, cx);
13141    });
13142    cx.assert_editor_state(indoc!(
13143        "fn a() {
13144             // dog();
13145        ˇ
13146             cat();
13147        }"
13148    ));
13149
13150    // Single cursor on one line -> advance
13151    // Cursor starts and ends at column 0
13152    cx.set_state(indoc!(
13153        "fn a() {
13154         ˇ    dog();
13155             cat();
13156        }"
13157    ));
13158    cx.update_editor(|editor, window, cx| {
13159        editor.toggle_comments(toggle_comments, window, cx);
13160    });
13161    cx.assert_editor_state(indoc!(
13162        "fn a() {
13163             // dog();
13164         ˇ    cat();
13165        }"
13166    ));
13167}
13168
13169#[gpui::test]
13170async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13171    init_test(cx, |_| {});
13172
13173    let mut cx = EditorTestContext::new(cx).await;
13174
13175    let html_language = Arc::new(
13176        Language::new(
13177            LanguageConfig {
13178                name: "HTML".into(),
13179                block_comment: Some(("<!-- ".into(), " -->".into())),
13180                ..Default::default()
13181            },
13182            Some(tree_sitter_html::LANGUAGE.into()),
13183        )
13184        .with_injection_query(
13185            r#"
13186            (script_element
13187                (raw_text) @injection.content
13188                (#set! injection.language "javascript"))
13189            "#,
13190        )
13191        .unwrap(),
13192    );
13193
13194    let javascript_language = Arc::new(Language::new(
13195        LanguageConfig {
13196            name: "JavaScript".into(),
13197            line_comments: vec!["// ".into()],
13198            ..Default::default()
13199        },
13200        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13201    ));
13202
13203    cx.language_registry().add(html_language.clone());
13204    cx.language_registry().add(javascript_language.clone());
13205    cx.update_buffer(|buffer, cx| {
13206        buffer.set_language(Some(html_language), cx);
13207    });
13208
13209    // Toggle comments for empty selections
13210    cx.set_state(
13211        &r#"
13212            <p>A</p>ˇ
13213            <p>B</p>ˇ
13214            <p>C</p>ˇ
13215        "#
13216        .unindent(),
13217    );
13218    cx.update_editor(|editor, window, cx| {
13219        editor.toggle_comments(&ToggleComments::default(), window, cx)
13220    });
13221    cx.assert_editor_state(
13222        &r#"
13223            <!-- <p>A</p>ˇ -->
13224            <!-- <p>B</p>ˇ -->
13225            <!-- <p>C</p>ˇ -->
13226        "#
13227        .unindent(),
13228    );
13229    cx.update_editor(|editor, window, cx| {
13230        editor.toggle_comments(&ToggleComments::default(), window, cx)
13231    });
13232    cx.assert_editor_state(
13233        &r#"
13234            <p>A</p>ˇ
13235            <p>B</p>ˇ
13236            <p>C</p>ˇ
13237        "#
13238        .unindent(),
13239    );
13240
13241    // Toggle comments for mixture of empty and non-empty selections, where
13242    // multiple selections occupy a given line.
13243    cx.set_state(
13244        &r#"
13245            <p>A«</p>
13246            <p>ˇ»B</p>ˇ
13247            <p>C«</p>
13248            <p>ˇ»D</p>ˇ
13249        "#
13250        .unindent(),
13251    );
13252
13253    cx.update_editor(|editor, window, cx| {
13254        editor.toggle_comments(&ToggleComments::default(), window, cx)
13255    });
13256    cx.assert_editor_state(
13257        &r#"
13258            <!-- <p>A«</p>
13259            <p>ˇ»B</p>ˇ -->
13260            <!-- <p>C«</p>
13261            <p>ˇ»D</p>ˇ -->
13262        "#
13263        .unindent(),
13264    );
13265    cx.update_editor(|editor, window, cx| {
13266        editor.toggle_comments(&ToggleComments::default(), window, cx)
13267    });
13268    cx.assert_editor_state(
13269        &r#"
13270            <p>A«</p>
13271            <p>ˇ»B</p>ˇ
13272            <p>C«</p>
13273            <p>ˇ»D</p>ˇ
13274        "#
13275        .unindent(),
13276    );
13277
13278    // Toggle comments when different languages are active for different
13279    // selections.
13280    cx.set_state(
13281        &r#"
13282            ˇ<script>
13283                ˇvar x = new Y();
13284            ˇ</script>
13285        "#
13286        .unindent(),
13287    );
13288    cx.executor().run_until_parked();
13289    cx.update_editor(|editor, window, cx| {
13290        editor.toggle_comments(&ToggleComments::default(), window, cx)
13291    });
13292    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13293    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13294    cx.assert_editor_state(
13295        &r#"
13296            <!-- ˇ<script> -->
13297                // ˇvar x = new Y();
13298            <!-- ˇ</script> -->
13299        "#
13300        .unindent(),
13301    );
13302}
13303
13304#[gpui::test]
13305fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13306    init_test(cx, |_| {});
13307
13308    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13309    let multibuffer = cx.new(|cx| {
13310        let mut multibuffer = MultiBuffer::new(ReadWrite);
13311        multibuffer.push_excerpts(
13312            buffer.clone(),
13313            [
13314                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13315                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13316            ],
13317            cx,
13318        );
13319        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13320        multibuffer
13321    });
13322
13323    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13324    editor.update_in(cx, |editor, window, cx| {
13325        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13326        editor.change_selections(None, window, cx, |s| {
13327            s.select_ranges([
13328                Point::new(0, 0)..Point::new(0, 0),
13329                Point::new(1, 0)..Point::new(1, 0),
13330            ])
13331        });
13332
13333        editor.handle_input("X", window, cx);
13334        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13335        assert_eq!(
13336            editor.selections.ranges(cx),
13337            [
13338                Point::new(0, 1)..Point::new(0, 1),
13339                Point::new(1, 1)..Point::new(1, 1),
13340            ]
13341        );
13342
13343        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13344        editor.change_selections(None, window, cx, |s| {
13345            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13346        });
13347        editor.backspace(&Default::default(), window, cx);
13348        assert_eq!(editor.text(cx), "Xa\nbbb");
13349        assert_eq!(
13350            editor.selections.ranges(cx),
13351            [Point::new(1, 0)..Point::new(1, 0)]
13352        );
13353
13354        editor.change_selections(None, window, cx, |s| {
13355            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13356        });
13357        editor.backspace(&Default::default(), window, cx);
13358        assert_eq!(editor.text(cx), "X\nbb");
13359        assert_eq!(
13360            editor.selections.ranges(cx),
13361            [Point::new(0, 1)..Point::new(0, 1)]
13362        );
13363    });
13364}
13365
13366#[gpui::test]
13367fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13368    init_test(cx, |_| {});
13369
13370    let markers = vec![('[', ']').into(), ('(', ')').into()];
13371    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13372        indoc! {"
13373            [aaaa
13374            (bbbb]
13375            cccc)",
13376        },
13377        markers.clone(),
13378    );
13379    let excerpt_ranges = markers.into_iter().map(|marker| {
13380        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13381        ExcerptRange::new(context.clone())
13382    });
13383    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13384    let multibuffer = cx.new(|cx| {
13385        let mut multibuffer = MultiBuffer::new(ReadWrite);
13386        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13387        multibuffer
13388    });
13389
13390    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13391    editor.update_in(cx, |editor, window, cx| {
13392        let (expected_text, selection_ranges) = marked_text_ranges(
13393            indoc! {"
13394                aaaa
13395                bˇbbb
13396                bˇbbˇb
13397                cccc"
13398            },
13399            true,
13400        );
13401        assert_eq!(editor.text(cx), expected_text);
13402        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
13403
13404        editor.handle_input("X", window, cx);
13405
13406        let (expected_text, expected_selections) = marked_text_ranges(
13407            indoc! {"
13408                aaaa
13409                bXˇbbXb
13410                bXˇbbXˇb
13411                cccc"
13412            },
13413            false,
13414        );
13415        assert_eq!(editor.text(cx), expected_text);
13416        assert_eq!(editor.selections.ranges(cx), expected_selections);
13417
13418        editor.newline(&Newline, window, cx);
13419        let (expected_text, expected_selections) = marked_text_ranges(
13420            indoc! {"
13421                aaaa
13422                bX
13423                ˇbbX
13424                b
13425                bX
13426                ˇbbX
13427                ˇb
13428                cccc"
13429            },
13430            false,
13431        );
13432        assert_eq!(editor.text(cx), expected_text);
13433        assert_eq!(editor.selections.ranges(cx), expected_selections);
13434    });
13435}
13436
13437#[gpui::test]
13438fn test_refresh_selections(cx: &mut TestAppContext) {
13439    init_test(cx, |_| {});
13440
13441    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13442    let mut excerpt1_id = None;
13443    let multibuffer = cx.new(|cx| {
13444        let mut multibuffer = MultiBuffer::new(ReadWrite);
13445        excerpt1_id = multibuffer
13446            .push_excerpts(
13447                buffer.clone(),
13448                [
13449                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13450                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13451                ],
13452                cx,
13453            )
13454            .into_iter()
13455            .next();
13456        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13457        multibuffer
13458    });
13459
13460    let editor = cx.add_window(|window, cx| {
13461        let mut editor = build_editor(multibuffer.clone(), window, cx);
13462        let snapshot = editor.snapshot(window, cx);
13463        editor.change_selections(None, window, cx, |s| {
13464            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13465        });
13466        editor.begin_selection(
13467            Point::new(2, 1).to_display_point(&snapshot),
13468            true,
13469            1,
13470            window,
13471            cx,
13472        );
13473        assert_eq!(
13474            editor.selections.ranges(cx),
13475            [
13476                Point::new(1, 3)..Point::new(1, 3),
13477                Point::new(2, 1)..Point::new(2, 1),
13478            ]
13479        );
13480        editor
13481    });
13482
13483    // Refreshing selections is a no-op when excerpts haven't changed.
13484    _ = editor.update(cx, |editor, window, cx| {
13485        editor.change_selections(None, window, cx, |s| s.refresh());
13486        assert_eq!(
13487            editor.selections.ranges(cx),
13488            [
13489                Point::new(1, 3)..Point::new(1, 3),
13490                Point::new(2, 1)..Point::new(2, 1),
13491            ]
13492        );
13493    });
13494
13495    multibuffer.update(cx, |multibuffer, cx| {
13496        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13497    });
13498    _ = editor.update(cx, |editor, window, cx| {
13499        // Removing an excerpt causes the first selection to become degenerate.
13500        assert_eq!(
13501            editor.selections.ranges(cx),
13502            [
13503                Point::new(0, 0)..Point::new(0, 0),
13504                Point::new(0, 1)..Point::new(0, 1)
13505            ]
13506        );
13507
13508        // Refreshing selections will relocate the first selection to the original buffer
13509        // location.
13510        editor.change_selections(None, window, cx, |s| s.refresh());
13511        assert_eq!(
13512            editor.selections.ranges(cx),
13513            [
13514                Point::new(0, 1)..Point::new(0, 1),
13515                Point::new(0, 3)..Point::new(0, 3)
13516            ]
13517        );
13518        assert!(editor.selections.pending_anchor().is_some());
13519    });
13520}
13521
13522#[gpui::test]
13523fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13524    init_test(cx, |_| {});
13525
13526    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13527    let mut excerpt1_id = None;
13528    let multibuffer = cx.new(|cx| {
13529        let mut multibuffer = MultiBuffer::new(ReadWrite);
13530        excerpt1_id = multibuffer
13531            .push_excerpts(
13532                buffer.clone(),
13533                [
13534                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13535                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13536                ],
13537                cx,
13538            )
13539            .into_iter()
13540            .next();
13541        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13542        multibuffer
13543    });
13544
13545    let editor = cx.add_window(|window, cx| {
13546        let mut editor = build_editor(multibuffer.clone(), window, cx);
13547        let snapshot = editor.snapshot(window, cx);
13548        editor.begin_selection(
13549            Point::new(1, 3).to_display_point(&snapshot),
13550            false,
13551            1,
13552            window,
13553            cx,
13554        );
13555        assert_eq!(
13556            editor.selections.ranges(cx),
13557            [Point::new(1, 3)..Point::new(1, 3)]
13558        );
13559        editor
13560    });
13561
13562    multibuffer.update(cx, |multibuffer, cx| {
13563        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13564    });
13565    _ = editor.update(cx, |editor, window, cx| {
13566        assert_eq!(
13567            editor.selections.ranges(cx),
13568            [Point::new(0, 0)..Point::new(0, 0)]
13569        );
13570
13571        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13572        editor.change_selections(None, window, cx, |s| s.refresh());
13573        assert_eq!(
13574            editor.selections.ranges(cx),
13575            [Point::new(0, 3)..Point::new(0, 3)]
13576        );
13577        assert!(editor.selections.pending_anchor().is_some());
13578    });
13579}
13580
13581#[gpui::test]
13582async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13583    init_test(cx, |_| {});
13584
13585    let language = Arc::new(
13586        Language::new(
13587            LanguageConfig {
13588                brackets: BracketPairConfig {
13589                    pairs: vec![
13590                        BracketPair {
13591                            start: "{".to_string(),
13592                            end: "}".to_string(),
13593                            close: true,
13594                            surround: true,
13595                            newline: true,
13596                        },
13597                        BracketPair {
13598                            start: "/* ".to_string(),
13599                            end: " */".to_string(),
13600                            close: true,
13601                            surround: true,
13602                            newline: true,
13603                        },
13604                    ],
13605                    ..Default::default()
13606                },
13607                ..Default::default()
13608            },
13609            Some(tree_sitter_rust::LANGUAGE.into()),
13610        )
13611        .with_indents_query("")
13612        .unwrap(),
13613    );
13614
13615    let text = concat!(
13616        "{   }\n",     //
13617        "  x\n",       //
13618        "  /*   */\n", //
13619        "x\n",         //
13620        "{{} }\n",     //
13621    );
13622
13623    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13624    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13625    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13626    editor
13627        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13628        .await;
13629
13630    editor.update_in(cx, |editor, window, cx| {
13631        editor.change_selections(None, window, cx, |s| {
13632            s.select_display_ranges([
13633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13634                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13635                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13636            ])
13637        });
13638        editor.newline(&Newline, window, cx);
13639
13640        assert_eq!(
13641            editor.buffer().read(cx).read(cx).text(),
13642            concat!(
13643                "{ \n",    // Suppress rustfmt
13644                "\n",      //
13645                "}\n",     //
13646                "  x\n",   //
13647                "  /* \n", //
13648                "  \n",    //
13649                "  */\n",  //
13650                "x\n",     //
13651                "{{} \n",  //
13652                "}\n",     //
13653            )
13654        );
13655    });
13656}
13657
13658#[gpui::test]
13659fn test_highlighted_ranges(cx: &mut TestAppContext) {
13660    init_test(cx, |_| {});
13661
13662    let editor = cx.add_window(|window, cx| {
13663        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13664        build_editor(buffer.clone(), window, cx)
13665    });
13666
13667    _ = editor.update(cx, |editor, window, cx| {
13668        struct Type1;
13669        struct Type2;
13670
13671        let buffer = editor.buffer.read(cx).snapshot(cx);
13672
13673        let anchor_range =
13674            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13675
13676        editor.highlight_background::<Type1>(
13677            &[
13678                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13679                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13680                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13681                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13682            ],
13683            |_| Hsla::red(),
13684            cx,
13685        );
13686        editor.highlight_background::<Type2>(
13687            &[
13688                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13689                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13690                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13691                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13692            ],
13693            |_| Hsla::green(),
13694            cx,
13695        );
13696
13697        let snapshot = editor.snapshot(window, cx);
13698        let mut highlighted_ranges = editor.background_highlights_in_range(
13699            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13700            &snapshot,
13701            cx.theme(),
13702        );
13703        // Enforce a consistent ordering based on color without relying on the ordering of the
13704        // highlight's `TypeId` which is non-executor.
13705        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13706        assert_eq!(
13707            highlighted_ranges,
13708            &[
13709                (
13710                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13711                    Hsla::red(),
13712                ),
13713                (
13714                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13715                    Hsla::red(),
13716                ),
13717                (
13718                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13719                    Hsla::green(),
13720                ),
13721                (
13722                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13723                    Hsla::green(),
13724                ),
13725            ]
13726        );
13727        assert_eq!(
13728            editor.background_highlights_in_range(
13729                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13730                &snapshot,
13731                cx.theme(),
13732            ),
13733            &[(
13734                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13735                Hsla::red(),
13736            )]
13737        );
13738    });
13739}
13740
13741#[gpui::test]
13742async fn test_following(cx: &mut TestAppContext) {
13743    init_test(cx, |_| {});
13744
13745    let fs = FakeFs::new(cx.executor());
13746    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13747
13748    let buffer = project.update(cx, |project, cx| {
13749        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13750        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13751    });
13752    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13753    let follower = cx.update(|cx| {
13754        cx.open_window(
13755            WindowOptions {
13756                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13757                    gpui::Point::new(px(0.), px(0.)),
13758                    gpui::Point::new(px(10.), px(80.)),
13759                ))),
13760                ..Default::default()
13761            },
13762            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13763        )
13764        .unwrap()
13765    });
13766
13767    let is_still_following = Rc::new(RefCell::new(true));
13768    let follower_edit_event_count = Rc::new(RefCell::new(0));
13769    let pending_update = Rc::new(RefCell::new(None));
13770    let leader_entity = leader.root(cx).unwrap();
13771    let follower_entity = follower.root(cx).unwrap();
13772    _ = follower.update(cx, {
13773        let update = pending_update.clone();
13774        let is_still_following = is_still_following.clone();
13775        let follower_edit_event_count = follower_edit_event_count.clone();
13776        |_, window, cx| {
13777            cx.subscribe_in(
13778                &leader_entity,
13779                window,
13780                move |_, leader, event, window, cx| {
13781                    leader.read(cx).add_event_to_update_proto(
13782                        event,
13783                        &mut update.borrow_mut(),
13784                        window,
13785                        cx,
13786                    );
13787                },
13788            )
13789            .detach();
13790
13791            cx.subscribe_in(
13792                &follower_entity,
13793                window,
13794                move |_, _, event: &EditorEvent, _window, _cx| {
13795                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
13796                        *is_still_following.borrow_mut() = false;
13797                    }
13798
13799                    if let EditorEvent::BufferEdited = event {
13800                        *follower_edit_event_count.borrow_mut() += 1;
13801                    }
13802                },
13803            )
13804            .detach();
13805        }
13806    });
13807
13808    // Update the selections only
13809    _ = leader.update(cx, |leader, window, cx| {
13810        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13811    });
13812    follower
13813        .update(cx, |follower, window, cx| {
13814            follower.apply_update_proto(
13815                &project,
13816                pending_update.borrow_mut().take().unwrap(),
13817                window,
13818                cx,
13819            )
13820        })
13821        .unwrap()
13822        .await
13823        .unwrap();
13824    _ = follower.update(cx, |follower, _, cx| {
13825        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
13826    });
13827    assert!(*is_still_following.borrow());
13828    assert_eq!(*follower_edit_event_count.borrow(), 0);
13829
13830    // Update the scroll position only
13831    _ = leader.update(cx, |leader, window, cx| {
13832        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13833    });
13834    follower
13835        .update(cx, |follower, window, cx| {
13836            follower.apply_update_proto(
13837                &project,
13838                pending_update.borrow_mut().take().unwrap(),
13839                window,
13840                cx,
13841            )
13842        })
13843        .unwrap()
13844        .await
13845        .unwrap();
13846    assert_eq!(
13847        follower
13848            .update(cx, |follower, _, cx| follower.scroll_position(cx))
13849            .unwrap(),
13850        gpui::Point::new(1.5, 3.5)
13851    );
13852    assert!(*is_still_following.borrow());
13853    assert_eq!(*follower_edit_event_count.borrow(), 0);
13854
13855    // Update the selections and scroll position. The follower's scroll position is updated
13856    // via autoscroll, not via the leader's exact scroll position.
13857    _ = leader.update(cx, |leader, window, cx| {
13858        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
13859        leader.request_autoscroll(Autoscroll::newest(), cx);
13860        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
13861    });
13862    follower
13863        .update(cx, |follower, window, cx| {
13864            follower.apply_update_proto(
13865                &project,
13866                pending_update.borrow_mut().take().unwrap(),
13867                window,
13868                cx,
13869            )
13870        })
13871        .unwrap()
13872        .await
13873        .unwrap();
13874    _ = follower.update(cx, |follower, _, cx| {
13875        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
13876        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
13877    });
13878    assert!(*is_still_following.borrow());
13879
13880    // Creating a pending selection that precedes another selection
13881    _ = leader.update(cx, |leader, window, cx| {
13882        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
13883        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
13884    });
13885    follower
13886        .update(cx, |follower, window, cx| {
13887            follower.apply_update_proto(
13888                &project,
13889                pending_update.borrow_mut().take().unwrap(),
13890                window,
13891                cx,
13892            )
13893        })
13894        .unwrap()
13895        .await
13896        .unwrap();
13897    _ = follower.update(cx, |follower, _, cx| {
13898        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
13899    });
13900    assert!(*is_still_following.borrow());
13901
13902    // Extend the pending selection so that it surrounds another selection
13903    _ = leader.update(cx, |leader, window, cx| {
13904        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
13905    });
13906    follower
13907        .update(cx, |follower, window, cx| {
13908            follower.apply_update_proto(
13909                &project,
13910                pending_update.borrow_mut().take().unwrap(),
13911                window,
13912                cx,
13913            )
13914        })
13915        .unwrap()
13916        .await
13917        .unwrap();
13918    _ = follower.update(cx, |follower, _, cx| {
13919        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
13920    });
13921
13922    // Scrolling locally breaks the follow
13923    _ = follower.update(cx, |follower, window, cx| {
13924        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
13925        follower.set_scroll_anchor(
13926            ScrollAnchor {
13927                anchor: top_anchor,
13928                offset: gpui::Point::new(0.0, 0.5),
13929            },
13930            window,
13931            cx,
13932        );
13933    });
13934    assert!(!(*is_still_following.borrow()));
13935}
13936
13937#[gpui::test]
13938async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
13939    init_test(cx, |_| {});
13940
13941    let fs = FakeFs::new(cx.executor());
13942    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13943    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13944    let pane = workspace
13945        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13946        .unwrap();
13947
13948    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13949
13950    let leader = pane.update_in(cx, |_, window, cx| {
13951        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
13952        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
13953    });
13954
13955    // Start following the editor when it has no excerpts.
13956    let mut state_message =
13957        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
13958    let workspace_entity = workspace.root(cx).unwrap();
13959    let follower_1 = cx
13960        .update_window(*workspace.deref(), |_, window, cx| {
13961            Editor::from_state_proto(
13962                workspace_entity,
13963                ViewId {
13964                    creator: CollaboratorId::PeerId(PeerId::default()),
13965                    id: 0,
13966                },
13967                &mut state_message,
13968                window,
13969                cx,
13970            )
13971        })
13972        .unwrap()
13973        .unwrap()
13974        .await
13975        .unwrap();
13976
13977    let update_message = Rc::new(RefCell::new(None));
13978    follower_1.update_in(cx, {
13979        let update = update_message.clone();
13980        |_, window, cx| {
13981            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
13982                leader.read(cx).add_event_to_update_proto(
13983                    event,
13984                    &mut update.borrow_mut(),
13985                    window,
13986                    cx,
13987                );
13988            })
13989            .detach();
13990        }
13991    });
13992
13993    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
13994        (
13995            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
13996            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
13997        )
13998    });
13999
14000    // Insert some excerpts.
14001    leader.update(cx, |leader, cx| {
14002        leader.buffer.update(cx, |multibuffer, cx| {
14003            multibuffer.set_excerpts_for_path(
14004                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14005                buffer_1.clone(),
14006                vec![
14007                    Point::row_range(0..3),
14008                    Point::row_range(1..6),
14009                    Point::row_range(12..15),
14010                ],
14011                0,
14012                cx,
14013            );
14014            multibuffer.set_excerpts_for_path(
14015                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14016                buffer_2.clone(),
14017                vec![Point::row_range(0..6), Point::row_range(8..12)],
14018                0,
14019                cx,
14020            );
14021        });
14022    });
14023
14024    // Apply the update of adding the excerpts.
14025    follower_1
14026        .update_in(cx, |follower, window, cx| {
14027            follower.apply_update_proto(
14028                &project,
14029                update_message.borrow().clone().unwrap(),
14030                window,
14031                cx,
14032            )
14033        })
14034        .await
14035        .unwrap();
14036    assert_eq!(
14037        follower_1.update(cx, |editor, cx| editor.text(cx)),
14038        leader.update(cx, |editor, cx| editor.text(cx))
14039    );
14040    update_message.borrow_mut().take();
14041
14042    // Start following separately after it already has excerpts.
14043    let mut state_message =
14044        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14045    let workspace_entity = workspace.root(cx).unwrap();
14046    let follower_2 = cx
14047        .update_window(*workspace.deref(), |_, window, cx| {
14048            Editor::from_state_proto(
14049                workspace_entity,
14050                ViewId {
14051                    creator: CollaboratorId::PeerId(PeerId::default()),
14052                    id: 0,
14053                },
14054                &mut state_message,
14055                window,
14056                cx,
14057            )
14058        })
14059        .unwrap()
14060        .unwrap()
14061        .await
14062        .unwrap();
14063    assert_eq!(
14064        follower_2.update(cx, |editor, cx| editor.text(cx)),
14065        leader.update(cx, |editor, cx| editor.text(cx))
14066    );
14067
14068    // Remove some excerpts.
14069    leader.update(cx, |leader, cx| {
14070        leader.buffer.update(cx, |multibuffer, cx| {
14071            let excerpt_ids = multibuffer.excerpt_ids();
14072            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14073            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14074        });
14075    });
14076
14077    // Apply the update of removing the excerpts.
14078    follower_1
14079        .update_in(cx, |follower, window, cx| {
14080            follower.apply_update_proto(
14081                &project,
14082                update_message.borrow().clone().unwrap(),
14083                window,
14084                cx,
14085            )
14086        })
14087        .await
14088        .unwrap();
14089    follower_2
14090        .update_in(cx, |follower, window, cx| {
14091            follower.apply_update_proto(
14092                &project,
14093                update_message.borrow().clone().unwrap(),
14094                window,
14095                cx,
14096            )
14097        })
14098        .await
14099        .unwrap();
14100    update_message.borrow_mut().take();
14101    assert_eq!(
14102        follower_1.update(cx, |editor, cx| editor.text(cx)),
14103        leader.update(cx, |editor, cx| editor.text(cx))
14104    );
14105}
14106
14107#[gpui::test]
14108async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14109    init_test(cx, |_| {});
14110
14111    let mut cx = EditorTestContext::new(cx).await;
14112    let lsp_store =
14113        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14114
14115    cx.set_state(indoc! {"
14116        ˇfn func(abc def: i32) -> u32 {
14117        }
14118    "});
14119
14120    cx.update(|_, cx| {
14121        lsp_store.update(cx, |lsp_store, cx| {
14122            lsp_store
14123                .update_diagnostics(
14124                    LanguageServerId(0),
14125                    lsp::PublishDiagnosticsParams {
14126                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14127                        version: None,
14128                        diagnostics: vec![
14129                            lsp::Diagnostic {
14130                                range: lsp::Range::new(
14131                                    lsp::Position::new(0, 11),
14132                                    lsp::Position::new(0, 12),
14133                                ),
14134                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14135                                ..Default::default()
14136                            },
14137                            lsp::Diagnostic {
14138                                range: lsp::Range::new(
14139                                    lsp::Position::new(0, 12),
14140                                    lsp::Position::new(0, 15),
14141                                ),
14142                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14143                                ..Default::default()
14144                            },
14145                            lsp::Diagnostic {
14146                                range: lsp::Range::new(
14147                                    lsp::Position::new(0, 25),
14148                                    lsp::Position::new(0, 28),
14149                                ),
14150                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14151                                ..Default::default()
14152                            },
14153                        ],
14154                    },
14155                    None,
14156                    DiagnosticSourceKind::Pushed,
14157                    &[],
14158                    cx,
14159                )
14160                .unwrap()
14161        });
14162    });
14163
14164    executor.run_until_parked();
14165
14166    cx.update_editor(|editor, window, cx| {
14167        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14168    });
14169
14170    cx.assert_editor_state(indoc! {"
14171        fn func(abc def: i32) -> ˇu32 {
14172        }
14173    "});
14174
14175    cx.update_editor(|editor, window, cx| {
14176        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14177    });
14178
14179    cx.assert_editor_state(indoc! {"
14180        fn func(abc ˇdef: i32) -> u32 {
14181        }
14182    "});
14183
14184    cx.update_editor(|editor, window, cx| {
14185        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14186    });
14187
14188    cx.assert_editor_state(indoc! {"
14189        fn func(abcˇ def: i32) -> u32 {
14190        }
14191    "});
14192
14193    cx.update_editor(|editor, window, cx| {
14194        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14195    });
14196
14197    cx.assert_editor_state(indoc! {"
14198        fn func(abc def: i32) -> ˇu32 {
14199        }
14200    "});
14201}
14202
14203#[gpui::test]
14204async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14205    init_test(cx, |_| {});
14206
14207    let mut cx = EditorTestContext::new(cx).await;
14208
14209    let diff_base = r#"
14210        use some::mod;
14211
14212        const A: u32 = 42;
14213
14214        fn main() {
14215            println!("hello");
14216
14217            println!("world");
14218        }
14219        "#
14220    .unindent();
14221
14222    // Edits are modified, removed, modified, added
14223    cx.set_state(
14224        &r#"
14225        use some::modified;
14226
14227        ˇ
14228        fn main() {
14229            println!("hello there");
14230
14231            println!("around the");
14232            println!("world");
14233        }
14234        "#
14235        .unindent(),
14236    );
14237
14238    cx.set_head_text(&diff_base);
14239    executor.run_until_parked();
14240
14241    cx.update_editor(|editor, window, cx| {
14242        //Wrap around the bottom of the buffer
14243        for _ in 0..3 {
14244            editor.go_to_next_hunk(&GoToHunk, window, cx);
14245        }
14246    });
14247
14248    cx.assert_editor_state(
14249        &r#"
14250        ˇuse some::modified;
14251
14252
14253        fn main() {
14254            println!("hello there");
14255
14256            println!("around the");
14257            println!("world");
14258        }
14259        "#
14260        .unindent(),
14261    );
14262
14263    cx.update_editor(|editor, window, cx| {
14264        //Wrap around the top of the buffer
14265        for _ in 0..2 {
14266            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14267        }
14268    });
14269
14270    cx.assert_editor_state(
14271        &r#"
14272        use some::modified;
14273
14274
14275        fn main() {
14276        ˇ    println!("hello there");
14277
14278            println!("around the");
14279            println!("world");
14280        }
14281        "#
14282        .unindent(),
14283    );
14284
14285    cx.update_editor(|editor, window, cx| {
14286        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14287    });
14288
14289    cx.assert_editor_state(
14290        &r#"
14291        use some::modified;
14292
14293        ˇ
14294        fn main() {
14295            println!("hello there");
14296
14297            println!("around the");
14298            println!("world");
14299        }
14300        "#
14301        .unindent(),
14302    );
14303
14304    cx.update_editor(|editor, window, cx| {
14305        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14306    });
14307
14308    cx.assert_editor_state(
14309        &r#"
14310        ˇuse some::modified;
14311
14312
14313        fn main() {
14314            println!("hello there");
14315
14316            println!("around the");
14317            println!("world");
14318        }
14319        "#
14320        .unindent(),
14321    );
14322
14323    cx.update_editor(|editor, window, cx| {
14324        for _ in 0..2 {
14325            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14326        }
14327    });
14328
14329    cx.assert_editor_state(
14330        &r#"
14331        use some::modified;
14332
14333
14334        fn main() {
14335        ˇ    println!("hello there");
14336
14337            println!("around the");
14338            println!("world");
14339        }
14340        "#
14341        .unindent(),
14342    );
14343
14344    cx.update_editor(|editor, window, cx| {
14345        editor.fold(&Fold, window, cx);
14346    });
14347
14348    cx.update_editor(|editor, window, cx| {
14349        editor.go_to_next_hunk(&GoToHunk, window, cx);
14350    });
14351
14352    cx.assert_editor_state(
14353        &r#"
14354        ˇuse some::modified;
14355
14356
14357        fn main() {
14358            println!("hello there");
14359
14360            println!("around the");
14361            println!("world");
14362        }
14363        "#
14364        .unindent(),
14365    );
14366}
14367
14368#[test]
14369fn test_split_words() {
14370    fn split(text: &str) -> Vec<&str> {
14371        split_words(text).collect()
14372    }
14373
14374    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14375    assert_eq!(split("hello_world"), &["hello_", "world"]);
14376    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14377    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14378    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14379    assert_eq!(split("helloworld"), &["helloworld"]);
14380
14381    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14382}
14383
14384#[gpui::test]
14385async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14386    init_test(cx, |_| {});
14387
14388    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14389    let mut assert = |before, after| {
14390        let _state_context = cx.set_state(before);
14391        cx.run_until_parked();
14392        cx.update_editor(|editor, window, cx| {
14393            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14394        });
14395        cx.run_until_parked();
14396        cx.assert_editor_state(after);
14397    };
14398
14399    // Outside bracket jumps to outside of matching bracket
14400    assert("console.logˇ(var);", "console.log(var)ˇ;");
14401    assert("console.log(var)ˇ;", "console.logˇ(var);");
14402
14403    // Inside bracket jumps to inside of matching bracket
14404    assert("console.log(ˇvar);", "console.log(varˇ);");
14405    assert("console.log(varˇ);", "console.log(ˇvar);");
14406
14407    // When outside a bracket and inside, favor jumping to the inside bracket
14408    assert(
14409        "console.log('foo', [1, 2, 3]ˇ);",
14410        "console.log(ˇ'foo', [1, 2, 3]);",
14411    );
14412    assert(
14413        "console.log(ˇ'foo', [1, 2, 3]);",
14414        "console.log('foo', [1, 2, 3]ˇ);",
14415    );
14416
14417    // Bias forward if two options are equally likely
14418    assert(
14419        "let result = curried_fun()ˇ();",
14420        "let result = curried_fun()()ˇ;",
14421    );
14422
14423    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14424    assert(
14425        indoc! {"
14426            function test() {
14427                console.log('test')ˇ
14428            }"},
14429        indoc! {"
14430            function test() {
14431                console.logˇ('test')
14432            }"},
14433    );
14434}
14435
14436#[gpui::test]
14437async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14438    init_test(cx, |_| {});
14439
14440    let fs = FakeFs::new(cx.executor());
14441    fs.insert_tree(
14442        path!("/a"),
14443        json!({
14444            "main.rs": "fn main() { let a = 5; }",
14445            "other.rs": "// Test file",
14446        }),
14447    )
14448    .await;
14449    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14450
14451    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14452    language_registry.add(Arc::new(Language::new(
14453        LanguageConfig {
14454            name: "Rust".into(),
14455            matcher: LanguageMatcher {
14456                path_suffixes: vec!["rs".to_string()],
14457                ..Default::default()
14458            },
14459            brackets: BracketPairConfig {
14460                pairs: vec![BracketPair {
14461                    start: "{".to_string(),
14462                    end: "}".to_string(),
14463                    close: true,
14464                    surround: true,
14465                    newline: true,
14466                }],
14467                disabled_scopes_by_bracket_ix: Vec::new(),
14468            },
14469            ..Default::default()
14470        },
14471        Some(tree_sitter_rust::LANGUAGE.into()),
14472    )));
14473    let mut fake_servers = language_registry.register_fake_lsp(
14474        "Rust",
14475        FakeLspAdapter {
14476            capabilities: lsp::ServerCapabilities {
14477                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14478                    first_trigger_character: "{".to_string(),
14479                    more_trigger_character: None,
14480                }),
14481                ..Default::default()
14482            },
14483            ..Default::default()
14484        },
14485    );
14486
14487    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14488
14489    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14490
14491    let worktree_id = workspace
14492        .update(cx, |workspace, _, cx| {
14493            workspace.project().update(cx, |project, cx| {
14494                project.worktrees(cx).next().unwrap().read(cx).id()
14495            })
14496        })
14497        .unwrap();
14498
14499    let buffer = project
14500        .update(cx, |project, cx| {
14501            project.open_local_buffer(path!("/a/main.rs"), cx)
14502        })
14503        .await
14504        .unwrap();
14505    let editor_handle = workspace
14506        .update(cx, |workspace, window, cx| {
14507            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14508        })
14509        .unwrap()
14510        .await
14511        .unwrap()
14512        .downcast::<Editor>()
14513        .unwrap();
14514
14515    cx.executor().start_waiting();
14516    let fake_server = fake_servers.next().await.unwrap();
14517
14518    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14519        |params, _| async move {
14520            assert_eq!(
14521                params.text_document_position.text_document.uri,
14522                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14523            );
14524            assert_eq!(
14525                params.text_document_position.position,
14526                lsp::Position::new(0, 21),
14527            );
14528
14529            Ok(Some(vec![lsp::TextEdit {
14530                new_text: "]".to_string(),
14531                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14532            }]))
14533        },
14534    );
14535
14536    editor_handle.update_in(cx, |editor, window, cx| {
14537        window.focus(&editor.focus_handle(cx));
14538        editor.change_selections(None, window, cx, |s| {
14539            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14540        });
14541        editor.handle_input("{", window, cx);
14542    });
14543
14544    cx.executor().run_until_parked();
14545
14546    buffer.update(cx, |buffer, _| {
14547        assert_eq!(
14548            buffer.text(),
14549            "fn main() { let a = {5}; }",
14550            "No extra braces from on type formatting should appear in the buffer"
14551        )
14552    });
14553}
14554
14555#[gpui::test]
14556async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14557    init_test(cx, |_| {});
14558
14559    let fs = FakeFs::new(cx.executor());
14560    fs.insert_tree(
14561        path!("/a"),
14562        json!({
14563            "main.rs": "fn main() { let a = 5; }",
14564            "other.rs": "// Test file",
14565        }),
14566    )
14567    .await;
14568
14569    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14570
14571    let server_restarts = Arc::new(AtomicUsize::new(0));
14572    let closure_restarts = Arc::clone(&server_restarts);
14573    let language_server_name = "test language server";
14574    let language_name: LanguageName = "Rust".into();
14575
14576    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14577    language_registry.add(Arc::new(Language::new(
14578        LanguageConfig {
14579            name: language_name.clone(),
14580            matcher: LanguageMatcher {
14581                path_suffixes: vec!["rs".to_string()],
14582                ..Default::default()
14583            },
14584            ..Default::default()
14585        },
14586        Some(tree_sitter_rust::LANGUAGE.into()),
14587    )));
14588    let mut fake_servers = language_registry.register_fake_lsp(
14589        "Rust",
14590        FakeLspAdapter {
14591            name: language_server_name,
14592            initialization_options: Some(json!({
14593                "testOptionValue": true
14594            })),
14595            initializer: Some(Box::new(move |fake_server| {
14596                let task_restarts = Arc::clone(&closure_restarts);
14597                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14598                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14599                    futures::future::ready(Ok(()))
14600                });
14601            })),
14602            ..Default::default()
14603        },
14604    );
14605
14606    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14607    let _buffer = project
14608        .update(cx, |project, cx| {
14609            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14610        })
14611        .await
14612        .unwrap();
14613    let _fake_server = fake_servers.next().await.unwrap();
14614    update_test_language_settings(cx, |language_settings| {
14615        language_settings.languages.insert(
14616            language_name.clone(),
14617            LanguageSettingsContent {
14618                tab_size: NonZeroU32::new(8),
14619                ..Default::default()
14620            },
14621        );
14622    });
14623    cx.executor().run_until_parked();
14624    assert_eq!(
14625        server_restarts.load(atomic::Ordering::Acquire),
14626        0,
14627        "Should not restart LSP server on an unrelated change"
14628    );
14629
14630    update_test_project_settings(cx, |project_settings| {
14631        project_settings.lsp.insert(
14632            "Some other server name".into(),
14633            LspSettings {
14634                binary: None,
14635                settings: None,
14636                initialization_options: Some(json!({
14637                    "some other init value": false
14638                })),
14639                enable_lsp_tasks: false,
14640            },
14641        );
14642    });
14643    cx.executor().run_until_parked();
14644    assert_eq!(
14645        server_restarts.load(atomic::Ordering::Acquire),
14646        0,
14647        "Should not restart LSP server on an unrelated LSP settings change"
14648    );
14649
14650    update_test_project_settings(cx, |project_settings| {
14651        project_settings.lsp.insert(
14652            language_server_name.into(),
14653            LspSettings {
14654                binary: None,
14655                settings: None,
14656                initialization_options: Some(json!({
14657                    "anotherInitValue": false
14658                })),
14659                enable_lsp_tasks: false,
14660            },
14661        );
14662    });
14663    cx.executor().run_until_parked();
14664    assert_eq!(
14665        server_restarts.load(atomic::Ordering::Acquire),
14666        1,
14667        "Should restart LSP server on a related LSP settings change"
14668    );
14669
14670    update_test_project_settings(cx, |project_settings| {
14671        project_settings.lsp.insert(
14672            language_server_name.into(),
14673            LspSettings {
14674                binary: None,
14675                settings: None,
14676                initialization_options: Some(json!({
14677                    "anotherInitValue": false
14678                })),
14679                enable_lsp_tasks: false,
14680            },
14681        );
14682    });
14683    cx.executor().run_until_parked();
14684    assert_eq!(
14685        server_restarts.load(atomic::Ordering::Acquire),
14686        1,
14687        "Should not restart LSP server on a related LSP settings change that is the same"
14688    );
14689
14690    update_test_project_settings(cx, |project_settings| {
14691        project_settings.lsp.insert(
14692            language_server_name.into(),
14693            LspSettings {
14694                binary: None,
14695                settings: None,
14696                initialization_options: None,
14697                enable_lsp_tasks: false,
14698            },
14699        );
14700    });
14701    cx.executor().run_until_parked();
14702    assert_eq!(
14703        server_restarts.load(atomic::Ordering::Acquire),
14704        2,
14705        "Should restart LSP server on another related LSP settings change"
14706    );
14707}
14708
14709#[gpui::test]
14710async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
14711    init_test(cx, |_| {});
14712
14713    let mut cx = EditorLspTestContext::new_rust(
14714        lsp::ServerCapabilities {
14715            completion_provider: Some(lsp::CompletionOptions {
14716                trigger_characters: Some(vec![".".to_string()]),
14717                resolve_provider: Some(true),
14718                ..Default::default()
14719            }),
14720            ..Default::default()
14721        },
14722        cx,
14723    )
14724    .await;
14725
14726    cx.set_state("fn main() { let a = 2ˇ; }");
14727    cx.simulate_keystroke(".");
14728    let completion_item = lsp::CompletionItem {
14729        label: "some".into(),
14730        kind: Some(lsp::CompletionItemKind::SNIPPET),
14731        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14732        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14733            kind: lsp::MarkupKind::Markdown,
14734            value: "```rust\nSome(2)\n```".to_string(),
14735        })),
14736        deprecated: Some(false),
14737        sort_text: Some("fffffff2".to_string()),
14738        filter_text: Some("some".to_string()),
14739        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14740        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14741            range: lsp::Range {
14742                start: lsp::Position {
14743                    line: 0,
14744                    character: 22,
14745                },
14746                end: lsp::Position {
14747                    line: 0,
14748                    character: 22,
14749                },
14750            },
14751            new_text: "Some(2)".to_string(),
14752        })),
14753        additional_text_edits: Some(vec![lsp::TextEdit {
14754            range: lsp::Range {
14755                start: lsp::Position {
14756                    line: 0,
14757                    character: 20,
14758                },
14759                end: lsp::Position {
14760                    line: 0,
14761                    character: 22,
14762                },
14763            },
14764            new_text: "".to_string(),
14765        }]),
14766        ..Default::default()
14767    };
14768
14769    let closure_completion_item = completion_item.clone();
14770    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14771        let task_completion_item = closure_completion_item.clone();
14772        async move {
14773            Ok(Some(lsp::CompletionResponse::Array(vec![
14774                task_completion_item,
14775            ])))
14776        }
14777    });
14778
14779    request.next().await;
14780
14781    cx.condition(|editor, _| editor.context_menu_visible())
14782        .await;
14783    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14784        editor
14785            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14786            .unwrap()
14787    });
14788    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
14789
14790    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14791        let task_completion_item = completion_item.clone();
14792        async move { Ok(task_completion_item) }
14793    })
14794    .next()
14795    .await
14796    .unwrap();
14797    apply_additional_edits.await.unwrap();
14798    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
14799}
14800
14801#[gpui::test]
14802async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
14803    init_test(cx, |_| {});
14804
14805    let mut cx = EditorLspTestContext::new_rust(
14806        lsp::ServerCapabilities {
14807            completion_provider: Some(lsp::CompletionOptions {
14808                trigger_characters: Some(vec![".".to_string()]),
14809                resolve_provider: Some(true),
14810                ..Default::default()
14811            }),
14812            ..Default::default()
14813        },
14814        cx,
14815    )
14816    .await;
14817
14818    cx.set_state("fn main() { let a = 2ˇ; }");
14819    cx.simulate_keystroke(".");
14820
14821    let item1 = lsp::CompletionItem {
14822        label: "method id()".to_string(),
14823        filter_text: Some("id".to_string()),
14824        detail: None,
14825        documentation: None,
14826        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14827            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14828            new_text: ".id".to_string(),
14829        })),
14830        ..lsp::CompletionItem::default()
14831    };
14832
14833    let item2 = lsp::CompletionItem {
14834        label: "other".to_string(),
14835        filter_text: Some("other".to_string()),
14836        detail: None,
14837        documentation: None,
14838        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14839            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14840            new_text: ".other".to_string(),
14841        })),
14842        ..lsp::CompletionItem::default()
14843    };
14844
14845    let item1 = item1.clone();
14846    cx.set_request_handler::<lsp::request::Completion, _, _>({
14847        let item1 = item1.clone();
14848        move |_, _, _| {
14849            let item1 = item1.clone();
14850            let item2 = item2.clone();
14851            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
14852        }
14853    })
14854    .next()
14855    .await;
14856
14857    cx.condition(|editor, _| editor.context_menu_visible())
14858        .await;
14859    cx.update_editor(|editor, _, _| {
14860        let context_menu = editor.context_menu.borrow_mut();
14861        let context_menu = context_menu
14862            .as_ref()
14863            .expect("Should have the context menu deployed");
14864        match context_menu {
14865            CodeContextMenu::Completions(completions_menu) => {
14866                let completions = completions_menu.completions.borrow_mut();
14867                assert_eq!(
14868                    completions
14869                        .iter()
14870                        .map(|completion| &completion.label.text)
14871                        .collect::<Vec<_>>(),
14872                    vec!["method id()", "other"]
14873                )
14874            }
14875            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14876        }
14877    });
14878
14879    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
14880        let item1 = item1.clone();
14881        move |_, item_to_resolve, _| {
14882            let item1 = item1.clone();
14883            async move {
14884                if item1 == item_to_resolve {
14885                    Ok(lsp::CompletionItem {
14886                        label: "method id()".to_string(),
14887                        filter_text: Some("id".to_string()),
14888                        detail: Some("Now resolved!".to_string()),
14889                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
14890                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14891                            range: lsp::Range::new(
14892                                lsp::Position::new(0, 22),
14893                                lsp::Position::new(0, 22),
14894                            ),
14895                            new_text: ".id".to_string(),
14896                        })),
14897                        ..lsp::CompletionItem::default()
14898                    })
14899                } else {
14900                    Ok(item_to_resolve)
14901                }
14902            }
14903        }
14904    })
14905    .next()
14906    .await
14907    .unwrap();
14908    cx.run_until_parked();
14909
14910    cx.update_editor(|editor, window, cx| {
14911        editor.context_menu_next(&Default::default(), window, cx);
14912    });
14913
14914    cx.update_editor(|editor, _, _| {
14915        let context_menu = editor.context_menu.borrow_mut();
14916        let context_menu = context_menu
14917            .as_ref()
14918            .expect("Should have the context menu deployed");
14919        match context_menu {
14920            CodeContextMenu::Completions(completions_menu) => {
14921                let completions = completions_menu.completions.borrow_mut();
14922                assert_eq!(
14923                    completions
14924                        .iter()
14925                        .map(|completion| &completion.label.text)
14926                        .collect::<Vec<_>>(),
14927                    vec!["method id() Now resolved!", "other"],
14928                    "Should update first completion label, but not second as the filter text did not match."
14929                );
14930            }
14931            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
14932        }
14933    });
14934}
14935
14936#[gpui::test]
14937async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
14938    init_test(cx, |_| {});
14939    let mut cx = EditorLspTestContext::new_rust(
14940        lsp::ServerCapabilities {
14941            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
14942            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14943            completion_provider: Some(lsp::CompletionOptions {
14944                resolve_provider: Some(true),
14945                ..Default::default()
14946            }),
14947            ..Default::default()
14948        },
14949        cx,
14950    )
14951    .await;
14952    cx.set_state(indoc! {"
14953        struct TestStruct {
14954            field: i32
14955        }
14956
14957        fn mainˇ() {
14958            let unused_var = 42;
14959            let test_struct = TestStruct { field: 42 };
14960        }
14961    "});
14962    let symbol_range = cx.lsp_range(indoc! {"
14963        struct TestStruct {
14964            field: i32
14965        }
14966
14967        «fn main»() {
14968            let unused_var = 42;
14969            let test_struct = TestStruct { field: 42 };
14970        }
14971    "});
14972    let mut hover_requests =
14973        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
14974            Ok(Some(lsp::Hover {
14975                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
14976                    kind: lsp::MarkupKind::Markdown,
14977                    value: "Function documentation".to_string(),
14978                }),
14979                range: Some(symbol_range),
14980            }))
14981        });
14982
14983    // Case 1: Test that code action menu hide hover popover
14984    cx.dispatch_action(Hover);
14985    hover_requests.next().await;
14986    cx.condition(|editor, _| editor.hover_state.visible()).await;
14987    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14988        move |_, _, _| async move {
14989            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14990                lsp::CodeAction {
14991                    title: "Remove unused variable".to_string(),
14992                    kind: Some(CodeActionKind::QUICKFIX),
14993                    edit: Some(lsp::WorkspaceEdit {
14994                        changes: Some(
14995                            [(
14996                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
14997                                vec![lsp::TextEdit {
14998                                    range: lsp::Range::new(
14999                                        lsp::Position::new(5, 4),
15000                                        lsp::Position::new(5, 27),
15001                                    ),
15002                                    new_text: "".to_string(),
15003                                }],
15004                            )]
15005                            .into_iter()
15006                            .collect(),
15007                        ),
15008                        ..Default::default()
15009                    }),
15010                    ..Default::default()
15011                },
15012            )]))
15013        },
15014    );
15015    cx.update_editor(|editor, window, cx| {
15016        editor.toggle_code_actions(
15017            &ToggleCodeActions {
15018                deployed_from: None,
15019                quick_launch: false,
15020            },
15021            window,
15022            cx,
15023        );
15024    });
15025    code_action_requests.next().await;
15026    cx.run_until_parked();
15027    cx.condition(|editor, _| editor.context_menu_visible())
15028        .await;
15029    cx.update_editor(|editor, _, _| {
15030        assert!(
15031            !editor.hover_state.visible(),
15032            "Hover popover should be hidden when code action menu is shown"
15033        );
15034        // Hide code actions
15035        editor.context_menu.take();
15036    });
15037
15038    // Case 2: Test that code completions hide hover popover
15039    cx.dispatch_action(Hover);
15040    hover_requests.next().await;
15041    cx.condition(|editor, _| editor.hover_state.visible()).await;
15042    let counter = Arc::new(AtomicUsize::new(0));
15043    let mut completion_requests =
15044        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15045            let counter = counter.clone();
15046            async move {
15047                counter.fetch_add(1, atomic::Ordering::Release);
15048                Ok(Some(lsp::CompletionResponse::Array(vec![
15049                    lsp::CompletionItem {
15050                        label: "main".into(),
15051                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15052                        detail: Some("() -> ()".to_string()),
15053                        ..Default::default()
15054                    },
15055                    lsp::CompletionItem {
15056                        label: "TestStruct".into(),
15057                        kind: Some(lsp::CompletionItemKind::STRUCT),
15058                        detail: Some("struct TestStruct".to_string()),
15059                        ..Default::default()
15060                    },
15061                ])))
15062            }
15063        });
15064    cx.update_editor(|editor, window, cx| {
15065        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15066    });
15067    completion_requests.next().await;
15068    cx.condition(|editor, _| editor.context_menu_visible())
15069        .await;
15070    cx.update_editor(|editor, _, _| {
15071        assert!(
15072            !editor.hover_state.visible(),
15073            "Hover popover should be hidden when completion menu is shown"
15074        );
15075    });
15076}
15077
15078#[gpui::test]
15079async fn test_completions_resolve_happens_once(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 unresolved_item_1 = lsp::CompletionItem {
15099        label: "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    let resolved_item_1 = lsp::CompletionItem {
15110        additional_text_edits: Some(vec![lsp::TextEdit {
15111            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15112            new_text: "!!".to_string(),
15113        }]),
15114        ..unresolved_item_1.clone()
15115    };
15116    let unresolved_item_2 = lsp::CompletionItem {
15117        label: "other".to_string(),
15118        filter_text: Some("other".to_string()),
15119        detail: None,
15120        documentation: None,
15121        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15122            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15123            new_text: ".other".to_string(),
15124        })),
15125        ..lsp::CompletionItem::default()
15126    };
15127    let resolved_item_2 = lsp::CompletionItem {
15128        additional_text_edits: Some(vec![lsp::TextEdit {
15129            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15130            new_text: "??".to_string(),
15131        }]),
15132        ..unresolved_item_2.clone()
15133    };
15134
15135    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15136    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15137    cx.lsp
15138        .server
15139        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15140            let unresolved_item_1 = unresolved_item_1.clone();
15141            let resolved_item_1 = resolved_item_1.clone();
15142            let unresolved_item_2 = unresolved_item_2.clone();
15143            let resolved_item_2 = resolved_item_2.clone();
15144            let resolve_requests_1 = resolve_requests_1.clone();
15145            let resolve_requests_2 = resolve_requests_2.clone();
15146            move |unresolved_request, _| {
15147                let unresolved_item_1 = unresolved_item_1.clone();
15148                let resolved_item_1 = resolved_item_1.clone();
15149                let unresolved_item_2 = unresolved_item_2.clone();
15150                let resolved_item_2 = resolved_item_2.clone();
15151                let resolve_requests_1 = resolve_requests_1.clone();
15152                let resolve_requests_2 = resolve_requests_2.clone();
15153                async move {
15154                    if unresolved_request == unresolved_item_1 {
15155                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15156                        Ok(resolved_item_1.clone())
15157                    } else if unresolved_request == unresolved_item_2 {
15158                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15159                        Ok(resolved_item_2.clone())
15160                    } else {
15161                        panic!("Unexpected completion item {unresolved_request:?}")
15162                    }
15163                }
15164            }
15165        })
15166        .detach();
15167
15168    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15169        let unresolved_item_1 = unresolved_item_1.clone();
15170        let unresolved_item_2 = unresolved_item_2.clone();
15171        async move {
15172            Ok(Some(lsp::CompletionResponse::Array(vec![
15173                unresolved_item_1,
15174                unresolved_item_2,
15175            ])))
15176        }
15177    })
15178    .next()
15179    .await;
15180
15181    cx.condition(|editor, _| editor.context_menu_visible())
15182        .await;
15183    cx.update_editor(|editor, _, _| {
15184        let context_menu = editor.context_menu.borrow_mut();
15185        let context_menu = context_menu
15186            .as_ref()
15187            .expect("Should have the context menu deployed");
15188        match context_menu {
15189            CodeContextMenu::Completions(completions_menu) => {
15190                let completions = completions_menu.completions.borrow_mut();
15191                assert_eq!(
15192                    completions
15193                        .iter()
15194                        .map(|completion| &completion.label.text)
15195                        .collect::<Vec<_>>(),
15196                    vec!["id", "other"]
15197                )
15198            }
15199            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15200        }
15201    });
15202    cx.run_until_parked();
15203
15204    cx.update_editor(|editor, window, cx| {
15205        editor.context_menu_next(&ContextMenuNext, window, cx);
15206    });
15207    cx.run_until_parked();
15208    cx.update_editor(|editor, window, cx| {
15209        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15210    });
15211    cx.run_until_parked();
15212    cx.update_editor(|editor, window, cx| {
15213        editor.context_menu_next(&ContextMenuNext, window, cx);
15214    });
15215    cx.run_until_parked();
15216    cx.update_editor(|editor, window, cx| {
15217        editor
15218            .compose_completion(&ComposeCompletion::default(), window, cx)
15219            .expect("No task returned")
15220    })
15221    .await
15222    .expect("Completion failed");
15223    cx.run_until_parked();
15224
15225    cx.update_editor(|editor, _, cx| {
15226        assert_eq!(
15227            resolve_requests_1.load(atomic::Ordering::Acquire),
15228            1,
15229            "Should always resolve once despite multiple selections"
15230        );
15231        assert_eq!(
15232            resolve_requests_2.load(atomic::Ordering::Acquire),
15233            1,
15234            "Should always resolve once after multiple selections and applying the completion"
15235        );
15236        assert_eq!(
15237            editor.text(cx),
15238            "fn main() { let a = ??.other; }",
15239            "Should use resolved data when applying the completion"
15240        );
15241    });
15242}
15243
15244#[gpui::test]
15245async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15246    init_test(cx, |_| {});
15247
15248    let item_0 = lsp::CompletionItem {
15249        label: "abs".into(),
15250        insert_text: Some("abs".into()),
15251        data: Some(json!({ "very": "special"})),
15252        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15253        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15254            lsp::InsertReplaceEdit {
15255                new_text: "abs".to_string(),
15256                insert: lsp::Range::default(),
15257                replace: lsp::Range::default(),
15258            },
15259        )),
15260        ..lsp::CompletionItem::default()
15261    };
15262    let items = iter::once(item_0.clone())
15263        .chain((11..51).map(|i| lsp::CompletionItem {
15264            label: format!("item_{}", i),
15265            insert_text: Some(format!("item_{}", i)),
15266            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15267            ..lsp::CompletionItem::default()
15268        }))
15269        .collect::<Vec<_>>();
15270
15271    let default_commit_characters = vec!["?".to_string()];
15272    let default_data = json!({ "default": "data"});
15273    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15274    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15275    let default_edit_range = lsp::Range {
15276        start: lsp::Position {
15277            line: 0,
15278            character: 5,
15279        },
15280        end: lsp::Position {
15281            line: 0,
15282            character: 5,
15283        },
15284    };
15285
15286    let mut cx = EditorLspTestContext::new_rust(
15287        lsp::ServerCapabilities {
15288            completion_provider: Some(lsp::CompletionOptions {
15289                trigger_characters: Some(vec![".".to_string()]),
15290                resolve_provider: Some(true),
15291                ..Default::default()
15292            }),
15293            ..Default::default()
15294        },
15295        cx,
15296    )
15297    .await;
15298
15299    cx.set_state("fn main() { let a = 2ˇ; }");
15300    cx.simulate_keystroke(".");
15301
15302    let completion_data = default_data.clone();
15303    let completion_characters = default_commit_characters.clone();
15304    let completion_items = items.clone();
15305    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15306        let default_data = completion_data.clone();
15307        let default_commit_characters = completion_characters.clone();
15308        let items = completion_items.clone();
15309        async move {
15310            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15311                items,
15312                item_defaults: Some(lsp::CompletionListItemDefaults {
15313                    data: Some(default_data.clone()),
15314                    commit_characters: Some(default_commit_characters.clone()),
15315                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15316                        default_edit_range,
15317                    )),
15318                    insert_text_format: Some(default_insert_text_format),
15319                    insert_text_mode: Some(default_insert_text_mode),
15320                }),
15321                ..lsp::CompletionList::default()
15322            })))
15323        }
15324    })
15325    .next()
15326    .await;
15327
15328    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15329    cx.lsp
15330        .server
15331        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15332            let closure_resolved_items = resolved_items.clone();
15333            move |item_to_resolve, _| {
15334                let closure_resolved_items = closure_resolved_items.clone();
15335                async move {
15336                    closure_resolved_items.lock().push(item_to_resolve.clone());
15337                    Ok(item_to_resolve)
15338                }
15339            }
15340        })
15341        .detach();
15342
15343    cx.condition(|editor, _| editor.context_menu_visible())
15344        .await;
15345    cx.run_until_parked();
15346    cx.update_editor(|editor, _, _| {
15347        let menu = editor.context_menu.borrow_mut();
15348        match menu.as_ref().expect("should have the completions menu") {
15349            CodeContextMenu::Completions(completions_menu) => {
15350                assert_eq!(
15351                    completions_menu
15352                        .entries
15353                        .borrow()
15354                        .iter()
15355                        .map(|mat| mat.string.clone())
15356                        .collect::<Vec<String>>(),
15357                    items
15358                        .iter()
15359                        .map(|completion| completion.label.clone())
15360                        .collect::<Vec<String>>()
15361                );
15362            }
15363            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15364        }
15365    });
15366    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15367    // with 4 from the end.
15368    assert_eq!(
15369        *resolved_items.lock(),
15370        [&items[0..16], &items[items.len() - 4..items.len()]]
15371            .concat()
15372            .iter()
15373            .cloned()
15374            .map(|mut item| {
15375                if item.data.is_none() {
15376                    item.data = Some(default_data.clone());
15377                }
15378                item
15379            })
15380            .collect::<Vec<lsp::CompletionItem>>(),
15381        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15382    );
15383    resolved_items.lock().clear();
15384
15385    cx.update_editor(|editor, window, cx| {
15386        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15387    });
15388    cx.run_until_parked();
15389    // Completions that have already been resolved are skipped.
15390    assert_eq!(
15391        *resolved_items.lock(),
15392        items[items.len() - 16..items.len() - 4]
15393            .iter()
15394            .cloned()
15395            .map(|mut item| {
15396                if item.data.is_none() {
15397                    item.data = Some(default_data.clone());
15398                }
15399                item
15400            })
15401            .collect::<Vec<lsp::CompletionItem>>()
15402    );
15403    resolved_items.lock().clear();
15404}
15405
15406#[gpui::test]
15407async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15408    init_test(cx, |_| {});
15409
15410    let mut cx = EditorLspTestContext::new(
15411        Language::new(
15412            LanguageConfig {
15413                matcher: LanguageMatcher {
15414                    path_suffixes: vec!["jsx".into()],
15415                    ..Default::default()
15416                },
15417                overrides: [(
15418                    "element".into(),
15419                    LanguageConfigOverride {
15420                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15421                        ..Default::default()
15422                    },
15423                )]
15424                .into_iter()
15425                .collect(),
15426                ..Default::default()
15427            },
15428            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15429        )
15430        .with_override_query("(jsx_self_closing_element) @element")
15431        .unwrap(),
15432        lsp::ServerCapabilities {
15433            completion_provider: Some(lsp::CompletionOptions {
15434                trigger_characters: Some(vec![":".to_string()]),
15435                ..Default::default()
15436            }),
15437            ..Default::default()
15438        },
15439        cx,
15440    )
15441    .await;
15442
15443    cx.lsp
15444        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15445            Ok(Some(lsp::CompletionResponse::Array(vec![
15446                lsp::CompletionItem {
15447                    label: "bg-blue".into(),
15448                    ..Default::default()
15449                },
15450                lsp::CompletionItem {
15451                    label: "bg-red".into(),
15452                    ..Default::default()
15453                },
15454                lsp::CompletionItem {
15455                    label: "bg-yellow".into(),
15456                    ..Default::default()
15457                },
15458            ])))
15459        });
15460
15461    cx.set_state(r#"<p class="bgˇ" />"#);
15462
15463    // Trigger completion when typing a dash, because the dash is an extra
15464    // word character in the 'element' scope, which contains the cursor.
15465    cx.simulate_keystroke("-");
15466    cx.executor().run_until_parked();
15467    cx.update_editor(|editor, _, _| {
15468        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15469        {
15470            assert_eq!(
15471                completion_menu_entries(&menu),
15472                &["bg-blue", "bg-red", "bg-yellow"]
15473            );
15474        } else {
15475            panic!("expected completion menu to be open");
15476        }
15477    });
15478
15479    cx.simulate_keystroke("l");
15480    cx.executor().run_until_parked();
15481    cx.update_editor(|editor, _, _| {
15482        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15483        {
15484            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15485        } else {
15486            panic!("expected completion menu to be open");
15487        }
15488    });
15489
15490    // When filtering completions, consider the character after the '-' to
15491    // be the start of a subword.
15492    cx.set_state(r#"<p class="yelˇ" />"#);
15493    cx.simulate_keystroke("l");
15494    cx.executor().run_until_parked();
15495    cx.update_editor(|editor, _, _| {
15496        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15497        {
15498            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15499        } else {
15500            panic!("expected completion menu to be open");
15501        }
15502    });
15503}
15504
15505fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15506    let entries = menu.entries.borrow();
15507    entries.iter().map(|mat| mat.string.clone()).collect()
15508}
15509
15510#[gpui::test]
15511async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15512    init_test(cx, |settings| {
15513        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
15514            FormatterList(vec![Formatter::Prettier].into()),
15515        ))
15516    });
15517
15518    let fs = FakeFs::new(cx.executor());
15519    fs.insert_file(path!("/file.ts"), Default::default()).await;
15520
15521    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15522    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15523
15524    language_registry.add(Arc::new(Language::new(
15525        LanguageConfig {
15526            name: "TypeScript".into(),
15527            matcher: LanguageMatcher {
15528                path_suffixes: vec!["ts".to_string()],
15529                ..Default::default()
15530            },
15531            ..Default::default()
15532        },
15533        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15534    )));
15535    update_test_language_settings(cx, |settings| {
15536        settings.defaults.prettier = Some(PrettierSettings {
15537            allowed: true,
15538            ..PrettierSettings::default()
15539        });
15540    });
15541
15542    let test_plugin = "test_plugin";
15543    let _ = language_registry.register_fake_lsp(
15544        "TypeScript",
15545        FakeLspAdapter {
15546            prettier_plugins: vec![test_plugin],
15547            ..Default::default()
15548        },
15549    );
15550
15551    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15552    let buffer = project
15553        .update(cx, |project, cx| {
15554            project.open_local_buffer(path!("/file.ts"), cx)
15555        })
15556        .await
15557        .unwrap();
15558
15559    let buffer_text = "one\ntwo\nthree\n";
15560    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15561    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15562    editor.update_in(cx, |editor, window, cx| {
15563        editor.set_text(buffer_text, window, cx)
15564    });
15565
15566    editor
15567        .update_in(cx, |editor, window, cx| {
15568            editor.perform_format(
15569                project.clone(),
15570                FormatTrigger::Manual,
15571                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15572                window,
15573                cx,
15574            )
15575        })
15576        .unwrap()
15577        .await;
15578    assert_eq!(
15579        editor.update(cx, |editor, cx| editor.text(cx)),
15580        buffer_text.to_string() + prettier_format_suffix,
15581        "Test prettier formatting was not applied to the original buffer text",
15582    );
15583
15584    update_test_language_settings(cx, |settings| {
15585        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15586    });
15587    let format = editor.update_in(cx, |editor, window, cx| {
15588        editor.perform_format(
15589            project.clone(),
15590            FormatTrigger::Manual,
15591            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15592            window,
15593            cx,
15594        )
15595    });
15596    format.await.unwrap();
15597    assert_eq!(
15598        editor.update(cx, |editor, cx| editor.text(cx)),
15599        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15600        "Autoformatting (via test prettier) was not applied to the original buffer text",
15601    );
15602}
15603
15604#[gpui::test]
15605async fn test_addition_reverts(cx: &mut TestAppContext) {
15606    init_test(cx, |_| {});
15607    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15608    let base_text = indoc! {r#"
15609        struct Row;
15610        struct Row1;
15611        struct Row2;
15612
15613        struct Row4;
15614        struct Row5;
15615        struct Row6;
15616
15617        struct Row8;
15618        struct Row9;
15619        struct Row10;"#};
15620
15621    // When addition hunks are not adjacent to carets, no hunk revert is performed
15622    assert_hunk_revert(
15623        indoc! {r#"struct Row;
15624                   struct Row1;
15625                   struct Row1.1;
15626                   struct Row1.2;
15627                   struct Row2;ˇ
15628
15629                   struct Row4;
15630                   struct Row5;
15631                   struct Row6;
15632
15633                   struct Row8;
15634                   ˇstruct Row9;
15635                   struct Row9.1;
15636                   struct Row9.2;
15637                   struct Row9.3;
15638                   struct Row10;"#},
15639        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15640        indoc! {r#"struct Row;
15641                   struct Row1;
15642                   struct Row1.1;
15643                   struct Row1.2;
15644                   struct Row2;ˇ
15645
15646                   struct Row4;
15647                   struct Row5;
15648                   struct Row6;
15649
15650                   struct Row8;
15651                   ˇstruct Row9;
15652                   struct Row9.1;
15653                   struct Row9.2;
15654                   struct Row9.3;
15655                   struct Row10;"#},
15656        base_text,
15657        &mut cx,
15658    );
15659    // Same for selections
15660    assert_hunk_revert(
15661        indoc! {r#"struct Row;
15662                   struct Row1;
15663                   struct Row2;
15664                   struct Row2.1;
15665                   struct Row2.2;
15666                   «ˇ
15667                   struct Row4;
15668                   struct» Row5;
15669                   «struct Row6;
15670                   ˇ»
15671                   struct Row9.1;
15672                   struct Row9.2;
15673                   struct Row9.3;
15674                   struct Row8;
15675                   struct Row9;
15676                   struct Row10;"#},
15677        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15678        indoc! {r#"struct Row;
15679                   struct Row1;
15680                   struct Row2;
15681                   struct Row2.1;
15682                   struct Row2.2;
15683                   «ˇ
15684                   struct Row4;
15685                   struct» Row5;
15686                   «struct Row6;
15687                   ˇ»
15688                   struct Row9.1;
15689                   struct Row9.2;
15690                   struct Row9.3;
15691                   struct Row8;
15692                   struct Row9;
15693                   struct Row10;"#},
15694        base_text,
15695        &mut cx,
15696    );
15697
15698    // When carets and selections intersect the addition hunks, those are reverted.
15699    // Adjacent carets got merged.
15700    assert_hunk_revert(
15701        indoc! {r#"struct Row;
15702                   ˇ// something on the top
15703                   struct Row1;
15704                   struct Row2;
15705                   struct Roˇw3.1;
15706                   struct Row2.2;
15707                   struct Row2.3;ˇ
15708
15709                   struct Row4;
15710                   struct ˇRow5.1;
15711                   struct Row5.2;
15712                   struct «Rowˇ»5.3;
15713                   struct Row5;
15714                   struct Row6;
15715                   ˇ
15716                   struct Row9.1;
15717                   struct «Rowˇ»9.2;
15718                   struct «ˇRow»9.3;
15719                   struct Row8;
15720                   struct Row9;
15721                   «ˇ// something on bottom»
15722                   struct Row10;"#},
15723        vec![
15724            DiffHunkStatusKind::Added,
15725            DiffHunkStatusKind::Added,
15726            DiffHunkStatusKind::Added,
15727            DiffHunkStatusKind::Added,
15728            DiffHunkStatusKind::Added,
15729        ],
15730        indoc! {r#"struct Row;
15731                   ˇstruct Row1;
15732                   struct Row2;
15733                   ˇ
15734                   struct Row4;
15735                   ˇstruct Row5;
15736                   struct Row6;
15737                   ˇ
15738                   ˇstruct Row8;
15739                   struct Row9;
15740                   ˇstruct Row10;"#},
15741        base_text,
15742        &mut cx,
15743    );
15744}
15745
15746#[gpui::test]
15747async fn test_modification_reverts(cx: &mut TestAppContext) {
15748    init_test(cx, |_| {});
15749    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15750    let base_text = indoc! {r#"
15751        struct Row;
15752        struct Row1;
15753        struct Row2;
15754
15755        struct Row4;
15756        struct Row5;
15757        struct Row6;
15758
15759        struct Row8;
15760        struct Row9;
15761        struct Row10;"#};
15762
15763    // Modification hunks behave the same as the addition ones.
15764    assert_hunk_revert(
15765        indoc! {r#"struct Row;
15766                   struct Row1;
15767                   struct Row33;
15768                   ˇ
15769                   struct Row4;
15770                   struct Row5;
15771                   struct Row6;
15772                   ˇ
15773                   struct Row99;
15774                   struct Row9;
15775                   struct Row10;"#},
15776        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15777        indoc! {r#"struct Row;
15778                   struct Row1;
15779                   struct Row33;
15780                   ˇ
15781                   struct Row4;
15782                   struct Row5;
15783                   struct Row6;
15784                   ˇ
15785                   struct Row99;
15786                   struct Row9;
15787                   struct Row10;"#},
15788        base_text,
15789        &mut cx,
15790    );
15791    assert_hunk_revert(
15792        indoc! {r#"struct Row;
15793                   struct Row1;
15794                   struct Row33;
15795                   «ˇ
15796                   struct Row4;
15797                   struct» Row5;
15798                   «struct Row6;
15799                   ˇ»
15800                   struct Row99;
15801                   struct Row9;
15802                   struct Row10;"#},
15803        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
15804        indoc! {r#"struct Row;
15805                   struct Row1;
15806                   struct Row33;
15807                   «ˇ
15808                   struct Row4;
15809                   struct» Row5;
15810                   «struct Row6;
15811                   ˇ»
15812                   struct Row99;
15813                   struct Row9;
15814                   struct Row10;"#},
15815        base_text,
15816        &mut cx,
15817    );
15818
15819    assert_hunk_revert(
15820        indoc! {r#"ˇstruct Row1.1;
15821                   struct Row1;
15822                   «ˇstr»uct Row22;
15823
15824                   struct ˇRow44;
15825                   struct Row5;
15826                   struct «Rˇ»ow66;ˇ
15827
15828                   «struˇ»ct Row88;
15829                   struct Row9;
15830                   struct Row1011;ˇ"#},
15831        vec![
15832            DiffHunkStatusKind::Modified,
15833            DiffHunkStatusKind::Modified,
15834            DiffHunkStatusKind::Modified,
15835            DiffHunkStatusKind::Modified,
15836            DiffHunkStatusKind::Modified,
15837            DiffHunkStatusKind::Modified,
15838        ],
15839        indoc! {r#"struct Row;
15840                   ˇstruct Row1;
15841                   struct Row2;
15842                   ˇ
15843                   struct Row4;
15844                   ˇstruct Row5;
15845                   struct Row6;
15846                   ˇ
15847                   struct Row8;
15848                   ˇstruct Row9;
15849                   struct Row10;ˇ"#},
15850        base_text,
15851        &mut cx,
15852    );
15853}
15854
15855#[gpui::test]
15856async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
15857    init_test(cx, |_| {});
15858    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15859    let base_text = indoc! {r#"
15860        one
15861
15862        two
15863        three
15864        "#};
15865
15866    cx.set_head_text(base_text);
15867    cx.set_state("\nˇ\n");
15868    cx.executor().run_until_parked();
15869    cx.update_editor(|editor, _window, cx| {
15870        editor.expand_selected_diff_hunks(cx);
15871    });
15872    cx.executor().run_until_parked();
15873    cx.update_editor(|editor, window, cx| {
15874        editor.backspace(&Default::default(), window, cx);
15875    });
15876    cx.run_until_parked();
15877    cx.assert_state_with_diff(
15878        indoc! {r#"
15879
15880        - two
15881        - threeˇ
15882        +
15883        "#}
15884        .to_string(),
15885    );
15886}
15887
15888#[gpui::test]
15889async fn test_deletion_reverts(cx: &mut TestAppContext) {
15890    init_test(cx, |_| {});
15891    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15892    let base_text = indoc! {r#"struct Row;
15893struct Row1;
15894struct Row2;
15895
15896struct Row4;
15897struct Row5;
15898struct Row6;
15899
15900struct Row8;
15901struct Row9;
15902struct Row10;"#};
15903
15904    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
15905    assert_hunk_revert(
15906        indoc! {r#"struct Row;
15907                   struct Row2;
15908
15909                   ˇstruct Row4;
15910                   struct Row5;
15911                   struct Row6;
15912                   ˇ
15913                   struct Row8;
15914                   struct Row10;"#},
15915        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15916        indoc! {r#"struct Row;
15917                   struct Row2;
15918
15919                   ˇstruct Row4;
15920                   struct Row5;
15921                   struct Row6;
15922                   ˇ
15923                   struct Row8;
15924                   struct Row10;"#},
15925        base_text,
15926        &mut cx,
15927    );
15928    assert_hunk_revert(
15929        indoc! {r#"struct Row;
15930                   struct Row2;
15931
15932                   «ˇstruct Row4;
15933                   struct» Row5;
15934                   «struct Row6;
15935                   ˇ»
15936                   struct Row8;
15937                   struct Row10;"#},
15938        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15939        indoc! {r#"struct Row;
15940                   struct Row2;
15941
15942                   «ˇstruct Row4;
15943                   struct» Row5;
15944                   «struct Row6;
15945                   ˇ»
15946                   struct Row8;
15947                   struct Row10;"#},
15948        base_text,
15949        &mut cx,
15950    );
15951
15952    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
15953    assert_hunk_revert(
15954        indoc! {r#"struct Row;
15955                   ˇstruct Row2;
15956
15957                   struct Row4;
15958                   struct Row5;
15959                   struct Row6;
15960
15961                   struct Row8;ˇ
15962                   struct Row10;"#},
15963        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
15964        indoc! {r#"struct Row;
15965                   struct Row1;
15966                   ˇstruct Row2;
15967
15968                   struct Row4;
15969                   struct Row5;
15970                   struct Row6;
15971
15972                   struct Row8;ˇ
15973                   struct Row9;
15974                   struct Row10;"#},
15975        base_text,
15976        &mut cx,
15977    );
15978    assert_hunk_revert(
15979        indoc! {r#"struct Row;
15980                   struct Row2«ˇ;
15981                   struct Row4;
15982                   struct» Row5;
15983                   «struct Row6;
15984
15985                   struct Row8;ˇ»
15986                   struct Row10;"#},
15987        vec![
15988            DiffHunkStatusKind::Deleted,
15989            DiffHunkStatusKind::Deleted,
15990            DiffHunkStatusKind::Deleted,
15991        ],
15992        indoc! {r#"struct Row;
15993                   struct Row1;
15994                   struct Row2«ˇ;
15995
15996                   struct Row4;
15997                   struct» Row5;
15998                   «struct Row6;
15999
16000                   struct Row8;ˇ»
16001                   struct Row9;
16002                   struct Row10;"#},
16003        base_text,
16004        &mut cx,
16005    );
16006}
16007
16008#[gpui::test]
16009async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16010    init_test(cx, |_| {});
16011
16012    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16013    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16014    let base_text_3 =
16015        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16016
16017    let text_1 = edit_first_char_of_every_line(base_text_1);
16018    let text_2 = edit_first_char_of_every_line(base_text_2);
16019    let text_3 = edit_first_char_of_every_line(base_text_3);
16020
16021    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16022    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16023    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16024
16025    let multibuffer = cx.new(|cx| {
16026        let mut multibuffer = MultiBuffer::new(ReadWrite);
16027        multibuffer.push_excerpts(
16028            buffer_1.clone(),
16029            [
16030                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16031                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16032                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16033            ],
16034            cx,
16035        );
16036        multibuffer.push_excerpts(
16037            buffer_2.clone(),
16038            [
16039                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16040                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16041                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16042            ],
16043            cx,
16044        );
16045        multibuffer.push_excerpts(
16046            buffer_3.clone(),
16047            [
16048                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16049                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16050                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16051            ],
16052            cx,
16053        );
16054        multibuffer
16055    });
16056
16057    let fs = FakeFs::new(cx.executor());
16058    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16059    let (editor, cx) = cx
16060        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16061    editor.update_in(cx, |editor, _window, cx| {
16062        for (buffer, diff_base) in [
16063            (buffer_1.clone(), base_text_1),
16064            (buffer_2.clone(), base_text_2),
16065            (buffer_3.clone(), base_text_3),
16066        ] {
16067            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16068            editor
16069                .buffer
16070                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16071        }
16072    });
16073    cx.executor().run_until_parked();
16074
16075    editor.update_in(cx, |editor, window, cx| {
16076        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}");
16077        editor.select_all(&SelectAll, window, cx);
16078        editor.git_restore(&Default::default(), window, cx);
16079    });
16080    cx.executor().run_until_parked();
16081
16082    // When all ranges are selected, all buffer hunks are reverted.
16083    editor.update(cx, |editor, cx| {
16084        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");
16085    });
16086    buffer_1.update(cx, |buffer, _| {
16087        assert_eq!(buffer.text(), base_text_1);
16088    });
16089    buffer_2.update(cx, |buffer, _| {
16090        assert_eq!(buffer.text(), base_text_2);
16091    });
16092    buffer_3.update(cx, |buffer, _| {
16093        assert_eq!(buffer.text(), base_text_3);
16094    });
16095
16096    editor.update_in(cx, |editor, window, cx| {
16097        editor.undo(&Default::default(), window, cx);
16098    });
16099
16100    editor.update_in(cx, |editor, window, cx| {
16101        editor.change_selections(None, window, cx, |s| {
16102            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16103        });
16104        editor.git_restore(&Default::default(), window, cx);
16105    });
16106
16107    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16108    // but not affect buffer_2 and its related excerpts.
16109    editor.update(cx, |editor, cx| {
16110        assert_eq!(
16111            editor.text(cx),
16112            "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}"
16113        );
16114    });
16115    buffer_1.update(cx, |buffer, _| {
16116        assert_eq!(buffer.text(), base_text_1);
16117    });
16118    buffer_2.update(cx, |buffer, _| {
16119        assert_eq!(
16120            buffer.text(),
16121            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16122        );
16123    });
16124    buffer_3.update(cx, |buffer, _| {
16125        assert_eq!(
16126            buffer.text(),
16127            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16128        );
16129    });
16130
16131    fn edit_first_char_of_every_line(text: &str) -> String {
16132        text.split('\n')
16133            .map(|line| format!("X{}", &line[1..]))
16134            .collect::<Vec<_>>()
16135            .join("\n")
16136    }
16137}
16138
16139#[gpui::test]
16140async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16141    init_test(cx, |_| {});
16142
16143    let cols = 4;
16144    let rows = 10;
16145    let sample_text_1 = sample_text(rows, cols, 'a');
16146    assert_eq!(
16147        sample_text_1,
16148        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16149    );
16150    let sample_text_2 = sample_text(rows, cols, 'l');
16151    assert_eq!(
16152        sample_text_2,
16153        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16154    );
16155    let sample_text_3 = sample_text(rows, cols, 'v');
16156    assert_eq!(
16157        sample_text_3,
16158        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16159    );
16160
16161    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16162    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16163    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16164
16165    let multi_buffer = cx.new(|cx| {
16166        let mut multibuffer = MultiBuffer::new(ReadWrite);
16167        multibuffer.push_excerpts(
16168            buffer_1.clone(),
16169            [
16170                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16171                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16172                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16173            ],
16174            cx,
16175        );
16176        multibuffer.push_excerpts(
16177            buffer_2.clone(),
16178            [
16179                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16180                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16181                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16182            ],
16183            cx,
16184        );
16185        multibuffer.push_excerpts(
16186            buffer_3.clone(),
16187            [
16188                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16189                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16190                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16191            ],
16192            cx,
16193        );
16194        multibuffer
16195    });
16196
16197    let fs = FakeFs::new(cx.executor());
16198    fs.insert_tree(
16199        "/a",
16200        json!({
16201            "main.rs": sample_text_1,
16202            "other.rs": sample_text_2,
16203            "lib.rs": sample_text_3,
16204        }),
16205    )
16206    .await;
16207    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16208    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16209    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16210    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16211        Editor::new(
16212            EditorMode::full(),
16213            multi_buffer,
16214            Some(project.clone()),
16215            window,
16216            cx,
16217        )
16218    });
16219    let multibuffer_item_id = workspace
16220        .update(cx, |workspace, window, cx| {
16221            assert!(
16222                workspace.active_item(cx).is_none(),
16223                "active item should be None before the first item is added"
16224            );
16225            workspace.add_item_to_active_pane(
16226                Box::new(multi_buffer_editor.clone()),
16227                None,
16228                true,
16229                window,
16230                cx,
16231            );
16232            let active_item = workspace
16233                .active_item(cx)
16234                .expect("should have an active item after adding the multi buffer");
16235            assert!(
16236                !active_item.is_singleton(cx),
16237                "A multi buffer was expected to active after adding"
16238            );
16239            active_item.item_id()
16240        })
16241        .unwrap();
16242    cx.executor().run_until_parked();
16243
16244    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16245        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16246            s.select_ranges(Some(1..2))
16247        });
16248        editor.open_excerpts(&OpenExcerpts, window, cx);
16249    });
16250    cx.executor().run_until_parked();
16251    let first_item_id = workspace
16252        .update(cx, |workspace, window, cx| {
16253            let active_item = workspace
16254                .active_item(cx)
16255                .expect("should have an active item after navigating into the 1st buffer");
16256            let first_item_id = active_item.item_id();
16257            assert_ne!(
16258                first_item_id, multibuffer_item_id,
16259                "Should navigate into the 1st buffer and activate it"
16260            );
16261            assert!(
16262                active_item.is_singleton(cx),
16263                "New active item should be a singleton buffer"
16264            );
16265            assert_eq!(
16266                active_item
16267                    .act_as::<Editor>(cx)
16268                    .expect("should have navigated into an editor for the 1st buffer")
16269                    .read(cx)
16270                    .text(cx),
16271                sample_text_1
16272            );
16273
16274            workspace
16275                .go_back(workspace.active_pane().downgrade(), window, cx)
16276                .detach_and_log_err(cx);
16277
16278            first_item_id
16279        })
16280        .unwrap();
16281    cx.executor().run_until_parked();
16282    workspace
16283        .update(cx, |workspace, _, cx| {
16284            let active_item = workspace
16285                .active_item(cx)
16286                .expect("should have an active item after navigating back");
16287            assert_eq!(
16288                active_item.item_id(),
16289                multibuffer_item_id,
16290                "Should navigate back to the multi buffer"
16291            );
16292            assert!(!active_item.is_singleton(cx));
16293        })
16294        .unwrap();
16295
16296    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16297        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16298            s.select_ranges(Some(39..40))
16299        });
16300        editor.open_excerpts(&OpenExcerpts, window, cx);
16301    });
16302    cx.executor().run_until_parked();
16303    let second_item_id = workspace
16304        .update(cx, |workspace, window, cx| {
16305            let active_item = workspace
16306                .active_item(cx)
16307                .expect("should have an active item after navigating into the 2nd buffer");
16308            let second_item_id = active_item.item_id();
16309            assert_ne!(
16310                second_item_id, multibuffer_item_id,
16311                "Should navigate away from the multibuffer"
16312            );
16313            assert_ne!(
16314                second_item_id, first_item_id,
16315                "Should navigate into the 2nd buffer and activate it"
16316            );
16317            assert!(
16318                active_item.is_singleton(cx),
16319                "New active item should be a singleton buffer"
16320            );
16321            assert_eq!(
16322                active_item
16323                    .act_as::<Editor>(cx)
16324                    .expect("should have navigated into an editor")
16325                    .read(cx)
16326                    .text(cx),
16327                sample_text_2
16328            );
16329
16330            workspace
16331                .go_back(workspace.active_pane().downgrade(), window, cx)
16332                .detach_and_log_err(cx);
16333
16334            second_item_id
16335        })
16336        .unwrap();
16337    cx.executor().run_until_parked();
16338    workspace
16339        .update(cx, |workspace, _, cx| {
16340            let active_item = workspace
16341                .active_item(cx)
16342                .expect("should have an active item after navigating back from the 2nd buffer");
16343            assert_eq!(
16344                active_item.item_id(),
16345                multibuffer_item_id,
16346                "Should navigate back from the 2nd buffer to the multi buffer"
16347            );
16348            assert!(!active_item.is_singleton(cx));
16349        })
16350        .unwrap();
16351
16352    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16353        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
16354            s.select_ranges(Some(70..70))
16355        });
16356        editor.open_excerpts(&OpenExcerpts, window, cx);
16357    });
16358    cx.executor().run_until_parked();
16359    workspace
16360        .update(cx, |workspace, window, cx| {
16361            let active_item = workspace
16362                .active_item(cx)
16363                .expect("should have an active item after navigating into the 3rd buffer");
16364            let third_item_id = active_item.item_id();
16365            assert_ne!(
16366                third_item_id, multibuffer_item_id,
16367                "Should navigate into the 3rd buffer and activate it"
16368            );
16369            assert_ne!(third_item_id, first_item_id);
16370            assert_ne!(third_item_id, second_item_id);
16371            assert!(
16372                active_item.is_singleton(cx),
16373                "New active item should be a singleton buffer"
16374            );
16375            assert_eq!(
16376                active_item
16377                    .act_as::<Editor>(cx)
16378                    .expect("should have navigated into an editor")
16379                    .read(cx)
16380                    .text(cx),
16381                sample_text_3
16382            );
16383
16384            workspace
16385                .go_back(workspace.active_pane().downgrade(), window, cx)
16386                .detach_and_log_err(cx);
16387        })
16388        .unwrap();
16389    cx.executor().run_until_parked();
16390    workspace
16391        .update(cx, |workspace, _, cx| {
16392            let active_item = workspace
16393                .active_item(cx)
16394                .expect("should have an active item after navigating back from the 3rd buffer");
16395            assert_eq!(
16396                active_item.item_id(),
16397                multibuffer_item_id,
16398                "Should navigate back from the 3rd buffer to the multi buffer"
16399            );
16400            assert!(!active_item.is_singleton(cx));
16401        })
16402        .unwrap();
16403}
16404
16405#[gpui::test]
16406async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16407    init_test(cx, |_| {});
16408
16409    let mut cx = EditorTestContext::new(cx).await;
16410
16411    let diff_base = r#"
16412        use some::mod;
16413
16414        const A: u32 = 42;
16415
16416        fn main() {
16417            println!("hello");
16418
16419            println!("world");
16420        }
16421        "#
16422    .unindent();
16423
16424    cx.set_state(
16425        &r#"
16426        use some::modified;
16427
16428        ˇ
16429        fn main() {
16430            println!("hello there");
16431
16432            println!("around the");
16433            println!("world");
16434        }
16435        "#
16436        .unindent(),
16437    );
16438
16439    cx.set_head_text(&diff_base);
16440    executor.run_until_parked();
16441
16442    cx.update_editor(|editor, window, cx| {
16443        editor.go_to_next_hunk(&GoToHunk, window, cx);
16444        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16445    });
16446    executor.run_until_parked();
16447    cx.assert_state_with_diff(
16448        r#"
16449          use some::modified;
16450
16451
16452          fn main() {
16453        -     println!("hello");
16454        + ˇ    println!("hello there");
16455
16456              println!("around the");
16457              println!("world");
16458          }
16459        "#
16460        .unindent(),
16461    );
16462
16463    cx.update_editor(|editor, window, cx| {
16464        for _ in 0..2 {
16465            editor.go_to_next_hunk(&GoToHunk, window, cx);
16466            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16467        }
16468    });
16469    executor.run_until_parked();
16470    cx.assert_state_with_diff(
16471        r#"
16472        - use some::mod;
16473        + ˇuse some::modified;
16474
16475
16476          fn main() {
16477        -     println!("hello");
16478        +     println!("hello there");
16479
16480        +     println!("around the");
16481              println!("world");
16482          }
16483        "#
16484        .unindent(),
16485    );
16486
16487    cx.update_editor(|editor, window, cx| {
16488        editor.go_to_next_hunk(&GoToHunk, window, cx);
16489        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16490    });
16491    executor.run_until_parked();
16492    cx.assert_state_with_diff(
16493        r#"
16494        - use some::mod;
16495        + use some::modified;
16496
16497        - const A: u32 = 42;
16498          ˇ
16499          fn main() {
16500        -     println!("hello");
16501        +     println!("hello there");
16502
16503        +     println!("around the");
16504              println!("world");
16505          }
16506        "#
16507        .unindent(),
16508    );
16509
16510    cx.update_editor(|editor, window, cx| {
16511        editor.cancel(&Cancel, window, cx);
16512    });
16513
16514    cx.assert_state_with_diff(
16515        r#"
16516          use some::modified;
16517
16518          ˇ
16519          fn main() {
16520              println!("hello there");
16521
16522              println!("around the");
16523              println!("world");
16524          }
16525        "#
16526        .unindent(),
16527    );
16528}
16529
16530#[gpui::test]
16531async fn test_diff_base_change_with_expanded_diff_hunks(
16532    executor: BackgroundExecutor,
16533    cx: &mut TestAppContext,
16534) {
16535    init_test(cx, |_| {});
16536
16537    let mut cx = EditorTestContext::new(cx).await;
16538
16539    let diff_base = r#"
16540        use some::mod1;
16541        use some::mod2;
16542
16543        const A: u32 = 42;
16544        const B: u32 = 42;
16545        const C: u32 = 42;
16546
16547        fn main() {
16548            println!("hello");
16549
16550            println!("world");
16551        }
16552        "#
16553    .unindent();
16554
16555    cx.set_state(
16556        &r#"
16557        use some::mod2;
16558
16559        const A: u32 = 42;
16560        const C: u32 = 42;
16561
16562        fn main(ˇ) {
16563            //println!("hello");
16564
16565            println!("world");
16566            //
16567            //
16568        }
16569        "#
16570        .unindent(),
16571    );
16572
16573    cx.set_head_text(&diff_base);
16574    executor.run_until_parked();
16575
16576    cx.update_editor(|editor, window, cx| {
16577        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16578    });
16579    executor.run_until_parked();
16580    cx.assert_state_with_diff(
16581        r#"
16582        - use some::mod1;
16583          use some::mod2;
16584
16585          const A: u32 = 42;
16586        - const B: u32 = 42;
16587          const C: u32 = 42;
16588
16589          fn main(ˇ) {
16590        -     println!("hello");
16591        +     //println!("hello");
16592
16593              println!("world");
16594        +     //
16595        +     //
16596          }
16597        "#
16598        .unindent(),
16599    );
16600
16601    cx.set_head_text("new diff base!");
16602    executor.run_until_parked();
16603    cx.assert_state_with_diff(
16604        r#"
16605        - new diff base!
16606        + use some::mod2;
16607        +
16608        + const A: u32 = 42;
16609        + const C: u32 = 42;
16610        +
16611        + fn main(ˇ) {
16612        +     //println!("hello");
16613        +
16614        +     println!("world");
16615        +     //
16616        +     //
16617        + }
16618        "#
16619        .unindent(),
16620    );
16621}
16622
16623#[gpui::test]
16624async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16625    init_test(cx, |_| {});
16626
16627    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16628    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16629    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16630    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16631    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16632    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16633
16634    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16635    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16636    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16637
16638    let multi_buffer = cx.new(|cx| {
16639        let mut multibuffer = MultiBuffer::new(ReadWrite);
16640        multibuffer.push_excerpts(
16641            buffer_1.clone(),
16642            [
16643                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16644                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16645                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16646            ],
16647            cx,
16648        );
16649        multibuffer.push_excerpts(
16650            buffer_2.clone(),
16651            [
16652                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16653                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16654                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16655            ],
16656            cx,
16657        );
16658        multibuffer.push_excerpts(
16659            buffer_3.clone(),
16660            [
16661                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16662                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16663                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16664            ],
16665            cx,
16666        );
16667        multibuffer
16668    });
16669
16670    let editor =
16671        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16672    editor
16673        .update(cx, |editor, _window, cx| {
16674            for (buffer, diff_base) in [
16675                (buffer_1.clone(), file_1_old),
16676                (buffer_2.clone(), file_2_old),
16677                (buffer_3.clone(), file_3_old),
16678            ] {
16679                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16680                editor
16681                    .buffer
16682                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16683            }
16684        })
16685        .unwrap();
16686
16687    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16688    cx.run_until_parked();
16689
16690    cx.assert_editor_state(
16691        &"
16692            ˇaaa
16693            ccc
16694            ddd
16695
16696            ggg
16697            hhh
16698
16699
16700            lll
16701            mmm
16702            NNN
16703
16704            qqq
16705            rrr
16706
16707            uuu
16708            111
16709            222
16710            333
16711
16712            666
16713            777
16714
16715            000
16716            !!!"
16717        .unindent(),
16718    );
16719
16720    cx.update_editor(|editor, window, cx| {
16721        editor.select_all(&SelectAll, window, cx);
16722        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16723    });
16724    cx.executor().run_until_parked();
16725
16726    cx.assert_state_with_diff(
16727        "
16728            «aaa
16729          - bbb
16730            ccc
16731            ddd
16732
16733            ggg
16734            hhh
16735
16736
16737            lll
16738            mmm
16739          - nnn
16740          + NNN
16741
16742            qqq
16743            rrr
16744
16745            uuu
16746            111
16747            222
16748            333
16749
16750          + 666
16751            777
16752
16753            000
16754            !!!ˇ»"
16755            .unindent(),
16756    );
16757}
16758
16759#[gpui::test]
16760async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
16761    init_test(cx, |_| {});
16762
16763    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
16764    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
16765
16766    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
16767    let multi_buffer = cx.new(|cx| {
16768        let mut multibuffer = MultiBuffer::new(ReadWrite);
16769        multibuffer.push_excerpts(
16770            buffer.clone(),
16771            [
16772                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
16773                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
16774                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
16775            ],
16776            cx,
16777        );
16778        multibuffer
16779    });
16780
16781    let editor =
16782        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16783    editor
16784        .update(cx, |editor, _window, cx| {
16785            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
16786            editor
16787                .buffer
16788                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
16789        })
16790        .unwrap();
16791
16792    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16793    cx.run_until_parked();
16794
16795    cx.update_editor(|editor, window, cx| {
16796        editor.expand_all_diff_hunks(&Default::default(), window, cx)
16797    });
16798    cx.executor().run_until_parked();
16799
16800    // When the start of a hunk coincides with the start of its excerpt,
16801    // the hunk is expanded. When the start of a a hunk is earlier than
16802    // the start of its excerpt, the hunk is not expanded.
16803    cx.assert_state_with_diff(
16804        "
16805            ˇaaa
16806          - bbb
16807          + BBB
16808
16809          - ddd
16810          - eee
16811          + DDD
16812          + EEE
16813            fff
16814
16815            iii
16816        "
16817        .unindent(),
16818    );
16819}
16820
16821#[gpui::test]
16822async fn test_edits_around_expanded_insertion_hunks(
16823    executor: BackgroundExecutor,
16824    cx: &mut TestAppContext,
16825) {
16826    init_test(cx, |_| {});
16827
16828    let mut cx = EditorTestContext::new(cx).await;
16829
16830    let diff_base = r#"
16831        use some::mod1;
16832        use some::mod2;
16833
16834        const A: u32 = 42;
16835
16836        fn main() {
16837            println!("hello");
16838
16839            println!("world");
16840        }
16841        "#
16842    .unindent();
16843    executor.run_until_parked();
16844    cx.set_state(
16845        &r#"
16846        use some::mod1;
16847        use some::mod2;
16848
16849        const A: u32 = 42;
16850        const B: u32 = 42;
16851        const C: u32 = 42;
16852        ˇ
16853
16854        fn main() {
16855            println!("hello");
16856
16857            println!("world");
16858        }
16859        "#
16860        .unindent(),
16861    );
16862
16863    cx.set_head_text(&diff_base);
16864    executor.run_until_parked();
16865
16866    cx.update_editor(|editor, window, cx| {
16867        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16868    });
16869    executor.run_until_parked();
16870
16871    cx.assert_state_with_diff(
16872        r#"
16873        use some::mod1;
16874        use some::mod2;
16875
16876        const A: u32 = 42;
16877      + const B: u32 = 42;
16878      + const C: u32 = 42;
16879      + ˇ
16880
16881        fn main() {
16882            println!("hello");
16883
16884            println!("world");
16885        }
16886      "#
16887        .unindent(),
16888    );
16889
16890    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
16891    executor.run_until_parked();
16892
16893    cx.assert_state_with_diff(
16894        r#"
16895        use some::mod1;
16896        use some::mod2;
16897
16898        const A: u32 = 42;
16899      + const B: u32 = 42;
16900      + const C: u32 = 42;
16901      + const D: u32 = 42;
16902      + ˇ
16903
16904        fn main() {
16905            println!("hello");
16906
16907            println!("world");
16908        }
16909      "#
16910        .unindent(),
16911    );
16912
16913    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
16914    executor.run_until_parked();
16915
16916    cx.assert_state_with_diff(
16917        r#"
16918        use some::mod1;
16919        use some::mod2;
16920
16921        const A: u32 = 42;
16922      + const B: u32 = 42;
16923      + const C: u32 = 42;
16924      + const D: u32 = 42;
16925      + const E: u32 = 42;
16926      + ˇ
16927
16928        fn main() {
16929            println!("hello");
16930
16931            println!("world");
16932        }
16933      "#
16934        .unindent(),
16935    );
16936
16937    cx.update_editor(|editor, window, cx| {
16938        editor.delete_line(&DeleteLine, window, cx);
16939    });
16940    executor.run_until_parked();
16941
16942    cx.assert_state_with_diff(
16943        r#"
16944        use some::mod1;
16945        use some::mod2;
16946
16947        const A: u32 = 42;
16948      + const B: u32 = 42;
16949      + const C: u32 = 42;
16950      + const D: u32 = 42;
16951      + const E: u32 = 42;
16952        ˇ
16953        fn main() {
16954            println!("hello");
16955
16956            println!("world");
16957        }
16958      "#
16959        .unindent(),
16960    );
16961
16962    cx.update_editor(|editor, window, cx| {
16963        editor.move_up(&MoveUp, window, cx);
16964        editor.delete_line(&DeleteLine, window, cx);
16965        editor.move_up(&MoveUp, window, cx);
16966        editor.delete_line(&DeleteLine, window, cx);
16967        editor.move_up(&MoveUp, window, cx);
16968        editor.delete_line(&DeleteLine, window, cx);
16969    });
16970    executor.run_until_parked();
16971    cx.assert_state_with_diff(
16972        r#"
16973        use some::mod1;
16974        use some::mod2;
16975
16976        const A: u32 = 42;
16977      + const B: u32 = 42;
16978        ˇ
16979        fn main() {
16980            println!("hello");
16981
16982            println!("world");
16983        }
16984      "#
16985        .unindent(),
16986    );
16987
16988    cx.update_editor(|editor, window, cx| {
16989        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
16990        editor.delete_line(&DeleteLine, window, cx);
16991    });
16992    executor.run_until_parked();
16993    cx.assert_state_with_diff(
16994        r#"
16995        ˇ
16996        fn main() {
16997            println!("hello");
16998
16999            println!("world");
17000        }
17001      "#
17002        .unindent(),
17003    );
17004}
17005
17006#[gpui::test]
17007async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17008    init_test(cx, |_| {});
17009
17010    let mut cx = EditorTestContext::new(cx).await;
17011    cx.set_head_text(indoc! { "
17012        one
17013        two
17014        three
17015        four
17016        five
17017        "
17018    });
17019    cx.set_state(indoc! { "
17020        one
17021        ˇthree
17022        five
17023    "});
17024    cx.run_until_parked();
17025    cx.update_editor(|editor, window, cx| {
17026        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17027    });
17028    cx.assert_state_with_diff(
17029        indoc! { "
17030        one
17031      - two
17032        ˇthree
17033      - four
17034        five
17035    "}
17036        .to_string(),
17037    );
17038    cx.update_editor(|editor, window, cx| {
17039        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17040    });
17041
17042    cx.assert_state_with_diff(
17043        indoc! { "
17044        one
17045        ˇthree
17046        five
17047    "}
17048        .to_string(),
17049    );
17050
17051    cx.set_state(indoc! { "
17052        one
17053        ˇTWO
17054        three
17055        four
17056        five
17057    "});
17058    cx.run_until_parked();
17059    cx.update_editor(|editor, window, cx| {
17060        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17061    });
17062
17063    cx.assert_state_with_diff(
17064        indoc! { "
17065            one
17066          - two
17067          + ˇTWO
17068            three
17069            four
17070            five
17071        "}
17072        .to_string(),
17073    );
17074    cx.update_editor(|editor, window, cx| {
17075        editor.move_up(&Default::default(), window, cx);
17076        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17077    });
17078    cx.assert_state_with_diff(
17079        indoc! { "
17080            one
17081            ˇTWO
17082            three
17083            four
17084            five
17085        "}
17086        .to_string(),
17087    );
17088}
17089
17090#[gpui::test]
17091async fn test_edits_around_expanded_deletion_hunks(
17092    executor: BackgroundExecutor,
17093    cx: &mut TestAppContext,
17094) {
17095    init_test(cx, |_| {});
17096
17097    let mut cx = EditorTestContext::new(cx).await;
17098
17099    let diff_base = r#"
17100        use some::mod1;
17101        use some::mod2;
17102
17103        const A: u32 = 42;
17104        const B: u32 = 42;
17105        const C: u32 = 42;
17106
17107
17108        fn main() {
17109            println!("hello");
17110
17111            println!("world");
17112        }
17113    "#
17114    .unindent();
17115    executor.run_until_parked();
17116    cx.set_state(
17117        &r#"
17118        use some::mod1;
17119        use some::mod2;
17120
17121        ˇconst B: u32 = 42;
17122        const C: u32 = 42;
17123
17124
17125        fn main() {
17126            println!("hello");
17127
17128            println!("world");
17129        }
17130        "#
17131        .unindent(),
17132    );
17133
17134    cx.set_head_text(&diff_base);
17135    executor.run_until_parked();
17136
17137    cx.update_editor(|editor, window, cx| {
17138        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17139    });
17140    executor.run_until_parked();
17141
17142    cx.assert_state_with_diff(
17143        r#"
17144        use some::mod1;
17145        use some::mod2;
17146
17147      - const A: u32 = 42;
17148        ˇconst B: u32 = 42;
17149        const C: u32 = 42;
17150
17151
17152        fn main() {
17153            println!("hello");
17154
17155            println!("world");
17156        }
17157      "#
17158        .unindent(),
17159    );
17160
17161    cx.update_editor(|editor, window, cx| {
17162        editor.delete_line(&DeleteLine, window, cx);
17163    });
17164    executor.run_until_parked();
17165    cx.assert_state_with_diff(
17166        r#"
17167        use some::mod1;
17168        use some::mod2;
17169
17170      - const A: u32 = 42;
17171      - const B: u32 = 42;
17172        ˇconst C: u32 = 42;
17173
17174
17175        fn main() {
17176            println!("hello");
17177
17178            println!("world");
17179        }
17180      "#
17181        .unindent(),
17182    );
17183
17184    cx.update_editor(|editor, window, cx| {
17185        editor.delete_line(&DeleteLine, window, cx);
17186    });
17187    executor.run_until_parked();
17188    cx.assert_state_with_diff(
17189        r#"
17190        use some::mod1;
17191        use some::mod2;
17192
17193      - const A: u32 = 42;
17194      - const B: u32 = 42;
17195      - const C: u32 = 42;
17196        ˇ
17197
17198        fn main() {
17199            println!("hello");
17200
17201            println!("world");
17202        }
17203      "#
17204        .unindent(),
17205    );
17206
17207    cx.update_editor(|editor, window, cx| {
17208        editor.handle_input("replacement", window, cx);
17209    });
17210    executor.run_until_parked();
17211    cx.assert_state_with_diff(
17212        r#"
17213        use some::mod1;
17214        use some::mod2;
17215
17216      - const A: u32 = 42;
17217      - const B: u32 = 42;
17218      - const C: u32 = 42;
17219      -
17220      + replacementˇ
17221
17222        fn main() {
17223            println!("hello");
17224
17225            println!("world");
17226        }
17227      "#
17228        .unindent(),
17229    );
17230}
17231
17232#[gpui::test]
17233async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17234    init_test(cx, |_| {});
17235
17236    let mut cx = EditorTestContext::new(cx).await;
17237
17238    let base_text = r#"
17239        one
17240        two
17241        three
17242        four
17243        five
17244    "#
17245    .unindent();
17246    executor.run_until_parked();
17247    cx.set_state(
17248        &r#"
17249        one
17250        two
17251        fˇour
17252        five
17253        "#
17254        .unindent(),
17255    );
17256
17257    cx.set_head_text(&base_text);
17258    executor.run_until_parked();
17259
17260    cx.update_editor(|editor, window, cx| {
17261        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17262    });
17263    executor.run_until_parked();
17264
17265    cx.assert_state_with_diff(
17266        r#"
17267          one
17268          two
17269        - three
17270          fˇour
17271          five
17272        "#
17273        .unindent(),
17274    );
17275
17276    cx.update_editor(|editor, window, cx| {
17277        editor.backspace(&Backspace, window, cx);
17278        editor.backspace(&Backspace, window, cx);
17279    });
17280    executor.run_until_parked();
17281    cx.assert_state_with_diff(
17282        r#"
17283          one
17284          two
17285        - threeˇ
17286        - four
17287        + our
17288          five
17289        "#
17290        .unindent(),
17291    );
17292}
17293
17294#[gpui::test]
17295async fn test_edit_after_expanded_modification_hunk(
17296    executor: BackgroundExecutor,
17297    cx: &mut TestAppContext,
17298) {
17299    init_test(cx, |_| {});
17300
17301    let mut cx = EditorTestContext::new(cx).await;
17302
17303    let diff_base = r#"
17304        use some::mod1;
17305        use some::mod2;
17306
17307        const A: u32 = 42;
17308        const B: u32 = 42;
17309        const C: u32 = 42;
17310        const D: u32 = 42;
17311
17312
17313        fn main() {
17314            println!("hello");
17315
17316            println!("world");
17317        }"#
17318    .unindent();
17319
17320    cx.set_state(
17321        &r#"
17322        use some::mod1;
17323        use some::mod2;
17324
17325        const A: u32 = 42;
17326        const B: u32 = 42;
17327        const C: u32 = 43ˇ
17328        const D: u32 = 42;
17329
17330
17331        fn main() {
17332            println!("hello");
17333
17334            println!("world");
17335        }"#
17336        .unindent(),
17337    );
17338
17339    cx.set_head_text(&diff_base);
17340    executor.run_until_parked();
17341    cx.update_editor(|editor, window, cx| {
17342        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17343    });
17344    executor.run_until_parked();
17345
17346    cx.assert_state_with_diff(
17347        r#"
17348        use some::mod1;
17349        use some::mod2;
17350
17351        const A: u32 = 42;
17352        const B: u32 = 42;
17353      - const C: u32 = 42;
17354      + const C: u32 = 43ˇ
17355        const D: u32 = 42;
17356
17357
17358        fn main() {
17359            println!("hello");
17360
17361            println!("world");
17362        }"#
17363        .unindent(),
17364    );
17365
17366    cx.update_editor(|editor, window, cx| {
17367        editor.handle_input("\nnew_line\n", window, cx);
17368    });
17369    executor.run_until_parked();
17370
17371    cx.assert_state_with_diff(
17372        r#"
17373        use some::mod1;
17374        use some::mod2;
17375
17376        const A: u32 = 42;
17377        const B: u32 = 42;
17378      - const C: u32 = 42;
17379      + const C: u32 = 43
17380      + new_line
17381      + ˇ
17382        const D: u32 = 42;
17383
17384
17385        fn main() {
17386            println!("hello");
17387
17388            println!("world");
17389        }"#
17390        .unindent(),
17391    );
17392}
17393
17394#[gpui::test]
17395async fn test_stage_and_unstage_added_file_hunk(
17396    executor: BackgroundExecutor,
17397    cx: &mut TestAppContext,
17398) {
17399    init_test(cx, |_| {});
17400
17401    let mut cx = EditorTestContext::new(cx).await;
17402    cx.update_editor(|editor, _, cx| {
17403        editor.set_expand_all_diff_hunks(cx);
17404    });
17405
17406    let working_copy = r#"
17407            ˇfn main() {
17408                println!("hello, world!");
17409            }
17410        "#
17411    .unindent();
17412
17413    cx.set_state(&working_copy);
17414    executor.run_until_parked();
17415
17416    cx.assert_state_with_diff(
17417        r#"
17418            + ˇfn main() {
17419            +     println!("hello, world!");
17420            + }
17421        "#
17422        .unindent(),
17423    );
17424    cx.assert_index_text(None);
17425
17426    cx.update_editor(|editor, window, cx| {
17427        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17428    });
17429    executor.run_until_parked();
17430    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17431    cx.assert_state_with_diff(
17432        r#"
17433            + ˇfn main() {
17434            +     println!("hello, world!");
17435            + }
17436        "#
17437        .unindent(),
17438    );
17439
17440    cx.update_editor(|editor, window, cx| {
17441        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17442    });
17443    executor.run_until_parked();
17444    cx.assert_index_text(None);
17445}
17446
17447async fn setup_indent_guides_editor(
17448    text: &str,
17449    cx: &mut TestAppContext,
17450) -> (BufferId, EditorTestContext) {
17451    init_test(cx, |_| {});
17452
17453    let mut cx = EditorTestContext::new(cx).await;
17454
17455    let buffer_id = cx.update_editor(|editor, window, cx| {
17456        editor.set_text(text, window, cx);
17457        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17458
17459        buffer_ids[0]
17460    });
17461
17462    (buffer_id, cx)
17463}
17464
17465fn assert_indent_guides(
17466    range: Range<u32>,
17467    expected: Vec<IndentGuide>,
17468    active_indices: Option<Vec<usize>>,
17469    cx: &mut EditorTestContext,
17470) {
17471    let indent_guides = cx.update_editor(|editor, window, cx| {
17472        let snapshot = editor.snapshot(window, cx).display_snapshot;
17473        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17474            editor,
17475            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17476            true,
17477            &snapshot,
17478            cx,
17479        );
17480
17481        indent_guides.sort_by(|a, b| {
17482            a.depth.cmp(&b.depth).then(
17483                a.start_row
17484                    .cmp(&b.start_row)
17485                    .then(a.end_row.cmp(&b.end_row)),
17486            )
17487        });
17488        indent_guides
17489    });
17490
17491    if let Some(expected) = active_indices {
17492        let active_indices = cx.update_editor(|editor, window, cx| {
17493            let snapshot = editor.snapshot(window, cx).display_snapshot;
17494            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17495        });
17496
17497        assert_eq!(
17498            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17499            expected,
17500            "Active indent guide indices do not match"
17501        );
17502    }
17503
17504    assert_eq!(indent_guides, expected, "Indent guides do not match");
17505}
17506
17507fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17508    IndentGuide {
17509        buffer_id,
17510        start_row: MultiBufferRow(start_row),
17511        end_row: MultiBufferRow(end_row),
17512        depth,
17513        tab_size: 4,
17514        settings: IndentGuideSettings {
17515            enabled: true,
17516            line_width: 1,
17517            active_line_width: 1,
17518            ..Default::default()
17519        },
17520    }
17521}
17522
17523#[gpui::test]
17524async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17525    let (buffer_id, mut cx) = setup_indent_guides_editor(
17526        &"
17527        fn main() {
17528            let a = 1;
17529        }"
17530        .unindent(),
17531        cx,
17532    )
17533    .await;
17534
17535    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17536}
17537
17538#[gpui::test]
17539async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17540    let (buffer_id, mut cx) = setup_indent_guides_editor(
17541        &"
17542        fn main() {
17543            let a = 1;
17544            let b = 2;
17545        }"
17546        .unindent(),
17547        cx,
17548    )
17549    .await;
17550
17551    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17552}
17553
17554#[gpui::test]
17555async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17556    let (buffer_id, mut cx) = setup_indent_guides_editor(
17557        &"
17558        fn main() {
17559            let a = 1;
17560            if a == 3 {
17561                let b = 2;
17562            } else {
17563                let c = 3;
17564            }
17565        }"
17566        .unindent(),
17567        cx,
17568    )
17569    .await;
17570
17571    assert_indent_guides(
17572        0..8,
17573        vec![
17574            indent_guide(buffer_id, 1, 6, 0),
17575            indent_guide(buffer_id, 3, 3, 1),
17576            indent_guide(buffer_id, 5, 5, 1),
17577        ],
17578        None,
17579        &mut cx,
17580    );
17581}
17582
17583#[gpui::test]
17584async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17585    let (buffer_id, mut cx) = setup_indent_guides_editor(
17586        &"
17587        fn main() {
17588            let a = 1;
17589                let b = 2;
17590            let c = 3;
17591        }"
17592        .unindent(),
17593        cx,
17594    )
17595    .await;
17596
17597    assert_indent_guides(
17598        0..5,
17599        vec![
17600            indent_guide(buffer_id, 1, 3, 0),
17601            indent_guide(buffer_id, 2, 2, 1),
17602        ],
17603        None,
17604        &mut cx,
17605    );
17606}
17607
17608#[gpui::test]
17609async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17610    let (buffer_id, mut cx) = setup_indent_guides_editor(
17611        &"
17612        fn main() {
17613            let a = 1;
17614
17615            let c = 3;
17616        }"
17617        .unindent(),
17618        cx,
17619    )
17620    .await;
17621
17622    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17623}
17624
17625#[gpui::test]
17626async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17627    let (buffer_id, mut cx) = setup_indent_guides_editor(
17628        &"
17629        fn main() {
17630            let a = 1;
17631
17632            let c = 3;
17633
17634            if a == 3 {
17635                let b = 2;
17636            } else {
17637                let c = 3;
17638            }
17639        }"
17640        .unindent(),
17641        cx,
17642    )
17643    .await;
17644
17645    assert_indent_guides(
17646        0..11,
17647        vec![
17648            indent_guide(buffer_id, 1, 9, 0),
17649            indent_guide(buffer_id, 6, 6, 1),
17650            indent_guide(buffer_id, 8, 8, 1),
17651        ],
17652        None,
17653        &mut cx,
17654    );
17655}
17656
17657#[gpui::test]
17658async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17659    let (buffer_id, mut cx) = setup_indent_guides_editor(
17660        &"
17661        fn main() {
17662            let a = 1;
17663
17664            let c = 3;
17665
17666            if a == 3 {
17667                let b = 2;
17668            } else {
17669                let c = 3;
17670            }
17671        }"
17672        .unindent(),
17673        cx,
17674    )
17675    .await;
17676
17677    assert_indent_guides(
17678        1..11,
17679        vec![
17680            indent_guide(buffer_id, 1, 9, 0),
17681            indent_guide(buffer_id, 6, 6, 1),
17682            indent_guide(buffer_id, 8, 8, 1),
17683        ],
17684        None,
17685        &mut cx,
17686    );
17687}
17688
17689#[gpui::test]
17690async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17691    let (buffer_id, mut cx) = setup_indent_guides_editor(
17692        &"
17693        fn main() {
17694            let a = 1;
17695
17696            let c = 3;
17697
17698            if a == 3 {
17699                let b = 2;
17700            } else {
17701                let c = 3;
17702            }
17703        }"
17704        .unindent(),
17705        cx,
17706    )
17707    .await;
17708
17709    assert_indent_guides(
17710        1..10,
17711        vec![
17712            indent_guide(buffer_id, 1, 9, 0),
17713            indent_guide(buffer_id, 6, 6, 1),
17714            indent_guide(buffer_id, 8, 8, 1),
17715        ],
17716        None,
17717        &mut cx,
17718    );
17719}
17720
17721#[gpui::test]
17722async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
17723    let (buffer_id, mut cx) = setup_indent_guides_editor(
17724        &"
17725        fn main() {
17726            if a {
17727                b(
17728                    c,
17729                    d,
17730                )
17731            } else {
17732                e(
17733                    f
17734                )
17735            }
17736        }"
17737        .unindent(),
17738        cx,
17739    )
17740    .await;
17741
17742    assert_indent_guides(
17743        0..11,
17744        vec![
17745            indent_guide(buffer_id, 1, 10, 0),
17746            indent_guide(buffer_id, 2, 5, 1),
17747            indent_guide(buffer_id, 7, 9, 1),
17748            indent_guide(buffer_id, 3, 4, 2),
17749            indent_guide(buffer_id, 8, 8, 2),
17750        ],
17751        None,
17752        &mut cx,
17753    );
17754
17755    cx.update_editor(|editor, window, cx| {
17756        editor.fold_at(MultiBufferRow(2), window, cx);
17757        assert_eq!(
17758            editor.display_text(cx),
17759            "
17760            fn main() {
17761                if a {
17762                    b(⋯
17763                    )
17764                } else {
17765                    e(
17766                        f
17767                    )
17768                }
17769            }"
17770            .unindent()
17771        );
17772    });
17773
17774    assert_indent_guides(
17775        0..11,
17776        vec![
17777            indent_guide(buffer_id, 1, 10, 0),
17778            indent_guide(buffer_id, 2, 5, 1),
17779            indent_guide(buffer_id, 7, 9, 1),
17780            indent_guide(buffer_id, 8, 8, 2),
17781        ],
17782        None,
17783        &mut cx,
17784    );
17785}
17786
17787#[gpui::test]
17788async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
17789    let (buffer_id, mut cx) = setup_indent_guides_editor(
17790        &"
17791        block1
17792            block2
17793                block3
17794                    block4
17795            block2
17796        block1
17797        block1"
17798            .unindent(),
17799        cx,
17800    )
17801    .await;
17802
17803    assert_indent_guides(
17804        1..10,
17805        vec![
17806            indent_guide(buffer_id, 1, 4, 0),
17807            indent_guide(buffer_id, 2, 3, 1),
17808            indent_guide(buffer_id, 3, 3, 2),
17809        ],
17810        None,
17811        &mut cx,
17812    );
17813}
17814
17815#[gpui::test]
17816async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
17817    let (buffer_id, mut cx) = setup_indent_guides_editor(
17818        &"
17819        block1
17820            block2
17821                block3
17822
17823        block1
17824        block1"
17825            .unindent(),
17826        cx,
17827    )
17828    .await;
17829
17830    assert_indent_guides(
17831        0..6,
17832        vec![
17833            indent_guide(buffer_id, 1, 2, 0),
17834            indent_guide(buffer_id, 2, 2, 1),
17835        ],
17836        None,
17837        &mut cx,
17838    );
17839}
17840
17841#[gpui::test]
17842async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
17843    let (buffer_id, mut cx) = setup_indent_guides_editor(
17844        &"
17845        function component() {
17846        \treturn (
17847        \t\t\t
17848        \t\t<div>
17849        \t\t\t<abc></abc>
17850        \t\t</div>
17851        \t)
17852        }"
17853        .unindent(),
17854        cx,
17855    )
17856    .await;
17857
17858    assert_indent_guides(
17859        0..8,
17860        vec![
17861            indent_guide(buffer_id, 1, 6, 0),
17862            indent_guide(buffer_id, 2, 5, 1),
17863            indent_guide(buffer_id, 4, 4, 2),
17864        ],
17865        None,
17866        &mut cx,
17867    );
17868}
17869
17870#[gpui::test]
17871async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
17872    let (buffer_id, mut cx) = setup_indent_guides_editor(
17873        &"
17874        function component() {
17875        \treturn (
17876        \t
17877        \t\t<div>
17878        \t\t\t<abc></abc>
17879        \t\t</div>
17880        \t)
17881        }"
17882        .unindent(),
17883        cx,
17884    )
17885    .await;
17886
17887    assert_indent_guides(
17888        0..8,
17889        vec![
17890            indent_guide(buffer_id, 1, 6, 0),
17891            indent_guide(buffer_id, 2, 5, 1),
17892            indent_guide(buffer_id, 4, 4, 2),
17893        ],
17894        None,
17895        &mut cx,
17896    );
17897}
17898
17899#[gpui::test]
17900async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
17901    let (buffer_id, mut cx) = setup_indent_guides_editor(
17902        &"
17903        block1
17904
17905
17906
17907            block2
17908        "
17909        .unindent(),
17910        cx,
17911    )
17912    .await;
17913
17914    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17915}
17916
17917#[gpui::test]
17918async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
17919    let (buffer_id, mut cx) = setup_indent_guides_editor(
17920        &"
17921        def a:
17922        \tb = 3
17923        \tif True:
17924        \t\tc = 4
17925        \t\td = 5
17926        \tprint(b)
17927        "
17928        .unindent(),
17929        cx,
17930    )
17931    .await;
17932
17933    assert_indent_guides(
17934        0..6,
17935        vec![
17936            indent_guide(buffer_id, 1, 5, 0),
17937            indent_guide(buffer_id, 3, 4, 1),
17938        ],
17939        None,
17940        &mut cx,
17941    );
17942}
17943
17944#[gpui::test]
17945async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
17946    let (buffer_id, mut cx) = setup_indent_guides_editor(
17947        &"
17948    fn main() {
17949        let a = 1;
17950    }"
17951        .unindent(),
17952        cx,
17953    )
17954    .await;
17955
17956    cx.update_editor(|editor, window, cx| {
17957        editor.change_selections(None, window, cx, |s| {
17958            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17959        });
17960    });
17961
17962    assert_indent_guides(
17963        0..3,
17964        vec![indent_guide(buffer_id, 1, 1, 0)],
17965        Some(vec![0]),
17966        &mut cx,
17967    );
17968}
17969
17970#[gpui::test]
17971async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
17972    let (buffer_id, mut cx) = setup_indent_guides_editor(
17973        &"
17974    fn main() {
17975        if 1 == 2 {
17976            let a = 1;
17977        }
17978    }"
17979        .unindent(),
17980        cx,
17981    )
17982    .await;
17983
17984    cx.update_editor(|editor, window, cx| {
17985        editor.change_selections(None, window, cx, |s| {
17986            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
17987        });
17988    });
17989
17990    assert_indent_guides(
17991        0..4,
17992        vec![
17993            indent_guide(buffer_id, 1, 3, 0),
17994            indent_guide(buffer_id, 2, 2, 1),
17995        ],
17996        Some(vec![1]),
17997        &mut cx,
17998    );
17999
18000    cx.update_editor(|editor, window, cx| {
18001        editor.change_selections(None, window, cx, |s| {
18002            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18003        });
18004    });
18005
18006    assert_indent_guides(
18007        0..4,
18008        vec![
18009            indent_guide(buffer_id, 1, 3, 0),
18010            indent_guide(buffer_id, 2, 2, 1),
18011        ],
18012        Some(vec![1]),
18013        &mut cx,
18014    );
18015
18016    cx.update_editor(|editor, window, cx| {
18017        editor.change_selections(None, window, cx, |s| {
18018            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18019        });
18020    });
18021
18022    assert_indent_guides(
18023        0..4,
18024        vec![
18025            indent_guide(buffer_id, 1, 3, 0),
18026            indent_guide(buffer_id, 2, 2, 1),
18027        ],
18028        Some(vec![0]),
18029        &mut cx,
18030    );
18031}
18032
18033#[gpui::test]
18034async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18035    let (buffer_id, mut cx) = setup_indent_guides_editor(
18036        &"
18037    fn main() {
18038        let a = 1;
18039
18040        let b = 2;
18041    }"
18042        .unindent(),
18043        cx,
18044    )
18045    .await;
18046
18047    cx.update_editor(|editor, window, cx| {
18048        editor.change_selections(None, window, cx, |s| {
18049            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18050        });
18051    });
18052
18053    assert_indent_guides(
18054        0..5,
18055        vec![indent_guide(buffer_id, 1, 3, 0)],
18056        Some(vec![0]),
18057        &mut cx,
18058    );
18059}
18060
18061#[gpui::test]
18062async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18063    let (buffer_id, mut cx) = setup_indent_guides_editor(
18064        &"
18065    def m:
18066        a = 1
18067        pass"
18068            .unindent(),
18069        cx,
18070    )
18071    .await;
18072
18073    cx.update_editor(|editor, window, cx| {
18074        editor.change_selections(None, window, cx, |s| {
18075            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18076        });
18077    });
18078
18079    assert_indent_guides(
18080        0..3,
18081        vec![indent_guide(buffer_id, 1, 2, 0)],
18082        Some(vec![0]),
18083        &mut cx,
18084    );
18085}
18086
18087#[gpui::test]
18088async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18089    init_test(cx, |_| {});
18090    let mut cx = EditorTestContext::new(cx).await;
18091    let text = indoc! {
18092        "
18093        impl A {
18094            fn b() {
18095                0;
18096                3;
18097                5;
18098                6;
18099                7;
18100            }
18101        }
18102        "
18103    };
18104    let base_text = indoc! {
18105        "
18106        impl A {
18107            fn b() {
18108                0;
18109                1;
18110                2;
18111                3;
18112                4;
18113            }
18114            fn c() {
18115                5;
18116                6;
18117                7;
18118            }
18119        }
18120        "
18121    };
18122
18123    cx.update_editor(|editor, window, cx| {
18124        editor.set_text(text, window, cx);
18125
18126        editor.buffer().update(cx, |multibuffer, cx| {
18127            let buffer = multibuffer.as_singleton().unwrap();
18128            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18129
18130            multibuffer.set_all_diff_hunks_expanded(cx);
18131            multibuffer.add_diff(diff, cx);
18132
18133            buffer.read(cx).remote_id()
18134        })
18135    });
18136    cx.run_until_parked();
18137
18138    cx.assert_state_with_diff(
18139        indoc! { "
18140          impl A {
18141              fn b() {
18142                  0;
18143        -         1;
18144        -         2;
18145                  3;
18146        -         4;
18147        -     }
18148        -     fn c() {
18149                  5;
18150                  6;
18151                  7;
18152              }
18153          }
18154          ˇ"
18155        }
18156        .to_string(),
18157    );
18158
18159    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18160        editor
18161            .snapshot(window, cx)
18162            .buffer_snapshot
18163            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18164            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18165            .collect::<Vec<_>>()
18166    });
18167    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18168    assert_eq!(
18169        actual_guides,
18170        vec![
18171            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18172            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18173            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18174        ]
18175    );
18176}
18177
18178#[gpui::test]
18179async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18180    init_test(cx, |_| {});
18181    let mut cx = EditorTestContext::new(cx).await;
18182
18183    let diff_base = r#"
18184        a
18185        b
18186        c
18187        "#
18188    .unindent();
18189
18190    cx.set_state(
18191        &r#"
18192        ˇA
18193        b
18194        C
18195        "#
18196        .unindent(),
18197    );
18198    cx.set_head_text(&diff_base);
18199    cx.update_editor(|editor, window, cx| {
18200        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18201    });
18202    executor.run_until_parked();
18203
18204    let both_hunks_expanded = r#"
18205        - a
18206        + ˇA
18207          b
18208        - c
18209        + C
18210        "#
18211    .unindent();
18212
18213    cx.assert_state_with_diff(both_hunks_expanded.clone());
18214
18215    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18216        let snapshot = editor.snapshot(window, cx);
18217        let hunks = editor
18218            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18219            .collect::<Vec<_>>();
18220        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18221        let buffer_id = hunks[0].buffer_id;
18222        hunks
18223            .into_iter()
18224            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18225            .collect::<Vec<_>>()
18226    });
18227    assert_eq!(hunk_ranges.len(), 2);
18228
18229    cx.update_editor(|editor, _, cx| {
18230        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18231    });
18232    executor.run_until_parked();
18233
18234    let second_hunk_expanded = r#"
18235          ˇA
18236          b
18237        - c
18238        + C
18239        "#
18240    .unindent();
18241
18242    cx.assert_state_with_diff(second_hunk_expanded);
18243
18244    cx.update_editor(|editor, _, cx| {
18245        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18246    });
18247    executor.run_until_parked();
18248
18249    cx.assert_state_with_diff(both_hunks_expanded.clone());
18250
18251    cx.update_editor(|editor, _, cx| {
18252        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18253    });
18254    executor.run_until_parked();
18255
18256    let first_hunk_expanded = r#"
18257        - a
18258        + ˇA
18259          b
18260          C
18261        "#
18262    .unindent();
18263
18264    cx.assert_state_with_diff(first_hunk_expanded);
18265
18266    cx.update_editor(|editor, _, cx| {
18267        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18268    });
18269    executor.run_until_parked();
18270
18271    cx.assert_state_with_diff(both_hunks_expanded);
18272
18273    cx.set_state(
18274        &r#"
18275        ˇA
18276        b
18277        "#
18278        .unindent(),
18279    );
18280    cx.run_until_parked();
18281
18282    // TODO this cursor position seems bad
18283    cx.assert_state_with_diff(
18284        r#"
18285        - ˇa
18286        + A
18287          b
18288        "#
18289        .unindent(),
18290    );
18291
18292    cx.update_editor(|editor, window, cx| {
18293        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18294    });
18295
18296    cx.assert_state_with_diff(
18297        r#"
18298            - ˇa
18299            + A
18300              b
18301            - c
18302            "#
18303        .unindent(),
18304    );
18305
18306    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18307        let snapshot = editor.snapshot(window, cx);
18308        let hunks = editor
18309            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18310            .collect::<Vec<_>>();
18311        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18312        let buffer_id = hunks[0].buffer_id;
18313        hunks
18314            .into_iter()
18315            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18316            .collect::<Vec<_>>()
18317    });
18318    assert_eq!(hunk_ranges.len(), 2);
18319
18320    cx.update_editor(|editor, _, cx| {
18321        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18322    });
18323    executor.run_until_parked();
18324
18325    cx.assert_state_with_diff(
18326        r#"
18327        - ˇa
18328        + A
18329          b
18330        "#
18331        .unindent(),
18332    );
18333}
18334
18335#[gpui::test]
18336async fn test_toggle_deletion_hunk_at_start_of_file(
18337    executor: BackgroundExecutor,
18338    cx: &mut TestAppContext,
18339) {
18340    init_test(cx, |_| {});
18341    let mut cx = EditorTestContext::new(cx).await;
18342
18343    let diff_base = r#"
18344        a
18345        b
18346        c
18347        "#
18348    .unindent();
18349
18350    cx.set_state(
18351        &r#"
18352        ˇb
18353        c
18354        "#
18355        .unindent(),
18356    );
18357    cx.set_head_text(&diff_base);
18358    cx.update_editor(|editor, window, cx| {
18359        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18360    });
18361    executor.run_until_parked();
18362
18363    let hunk_expanded = r#"
18364        - a
18365          ˇb
18366          c
18367        "#
18368    .unindent();
18369
18370    cx.assert_state_with_diff(hunk_expanded.clone());
18371
18372    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18373        let snapshot = editor.snapshot(window, cx);
18374        let hunks = editor
18375            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18376            .collect::<Vec<_>>();
18377        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18378        let buffer_id = hunks[0].buffer_id;
18379        hunks
18380            .into_iter()
18381            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18382            .collect::<Vec<_>>()
18383    });
18384    assert_eq!(hunk_ranges.len(), 1);
18385
18386    cx.update_editor(|editor, _, cx| {
18387        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18388    });
18389    executor.run_until_parked();
18390
18391    let hunk_collapsed = r#"
18392          ˇb
18393          c
18394        "#
18395    .unindent();
18396
18397    cx.assert_state_with_diff(hunk_collapsed);
18398
18399    cx.update_editor(|editor, _, cx| {
18400        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18401    });
18402    executor.run_until_parked();
18403
18404    cx.assert_state_with_diff(hunk_expanded.clone());
18405}
18406
18407#[gpui::test]
18408async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18409    init_test(cx, |_| {});
18410
18411    let fs = FakeFs::new(cx.executor());
18412    fs.insert_tree(
18413        path!("/test"),
18414        json!({
18415            ".git": {},
18416            "file-1": "ONE\n",
18417            "file-2": "TWO\n",
18418            "file-3": "THREE\n",
18419        }),
18420    )
18421    .await;
18422
18423    fs.set_head_for_repo(
18424        path!("/test/.git").as_ref(),
18425        &[
18426            ("file-1".into(), "one\n".into()),
18427            ("file-2".into(), "two\n".into()),
18428            ("file-3".into(), "three\n".into()),
18429        ],
18430        "deadbeef",
18431    );
18432
18433    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18434    let mut buffers = vec![];
18435    for i in 1..=3 {
18436        let buffer = project
18437            .update(cx, |project, cx| {
18438                let path = format!(path!("/test/file-{}"), i);
18439                project.open_local_buffer(path, cx)
18440            })
18441            .await
18442            .unwrap();
18443        buffers.push(buffer);
18444    }
18445
18446    let multibuffer = cx.new(|cx| {
18447        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18448        multibuffer.set_all_diff_hunks_expanded(cx);
18449        for buffer in &buffers {
18450            let snapshot = buffer.read(cx).snapshot();
18451            multibuffer.set_excerpts_for_path(
18452                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18453                buffer.clone(),
18454                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18455                DEFAULT_MULTIBUFFER_CONTEXT,
18456                cx,
18457            );
18458        }
18459        multibuffer
18460    });
18461
18462    let editor = cx.add_window(|window, cx| {
18463        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18464    });
18465    cx.run_until_parked();
18466
18467    let snapshot = editor
18468        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18469        .unwrap();
18470    let hunks = snapshot
18471        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18472        .map(|hunk| match hunk {
18473            DisplayDiffHunk::Unfolded {
18474                display_row_range, ..
18475            } => display_row_range,
18476            DisplayDiffHunk::Folded { .. } => unreachable!(),
18477        })
18478        .collect::<Vec<_>>();
18479    assert_eq!(
18480        hunks,
18481        [
18482            DisplayRow(2)..DisplayRow(4),
18483            DisplayRow(7)..DisplayRow(9),
18484            DisplayRow(12)..DisplayRow(14),
18485        ]
18486    );
18487}
18488
18489#[gpui::test]
18490async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18491    init_test(cx, |_| {});
18492
18493    let mut cx = EditorTestContext::new(cx).await;
18494    cx.set_head_text(indoc! { "
18495        one
18496        two
18497        three
18498        four
18499        five
18500        "
18501    });
18502    cx.set_index_text(indoc! { "
18503        one
18504        two
18505        three
18506        four
18507        five
18508        "
18509    });
18510    cx.set_state(indoc! {"
18511        one
18512        TWO
18513        ˇTHREE
18514        FOUR
18515        five
18516    "});
18517    cx.run_until_parked();
18518    cx.update_editor(|editor, window, cx| {
18519        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18520    });
18521    cx.run_until_parked();
18522    cx.assert_index_text(Some(indoc! {"
18523        one
18524        TWO
18525        THREE
18526        FOUR
18527        five
18528    "}));
18529    cx.set_state(indoc! { "
18530        one
18531        TWO
18532        ˇTHREE-HUNDRED
18533        FOUR
18534        five
18535    "});
18536    cx.run_until_parked();
18537    cx.update_editor(|editor, window, cx| {
18538        let snapshot = editor.snapshot(window, cx);
18539        let hunks = editor
18540            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18541            .collect::<Vec<_>>();
18542        assert_eq!(hunks.len(), 1);
18543        assert_eq!(
18544            hunks[0].status(),
18545            DiffHunkStatus {
18546                kind: DiffHunkStatusKind::Modified,
18547                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18548            }
18549        );
18550
18551        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18552    });
18553    cx.run_until_parked();
18554    cx.assert_index_text(Some(indoc! {"
18555        one
18556        TWO
18557        THREE-HUNDRED
18558        FOUR
18559        five
18560    "}));
18561}
18562
18563#[gpui::test]
18564fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18565    init_test(cx, |_| {});
18566
18567    let editor = cx.add_window(|window, cx| {
18568        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18569        build_editor(buffer, window, cx)
18570    });
18571
18572    let render_args = Arc::new(Mutex::new(None));
18573    let snapshot = editor
18574        .update(cx, |editor, window, cx| {
18575            let snapshot = editor.buffer().read(cx).snapshot(cx);
18576            let range =
18577                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18578
18579            struct RenderArgs {
18580                row: MultiBufferRow,
18581                folded: bool,
18582                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18583            }
18584
18585            let crease = Crease::inline(
18586                range,
18587                FoldPlaceholder::test(),
18588                {
18589                    let toggle_callback = render_args.clone();
18590                    move |row, folded, callback, _window, _cx| {
18591                        *toggle_callback.lock() = Some(RenderArgs {
18592                            row,
18593                            folded,
18594                            callback,
18595                        });
18596                        div()
18597                    }
18598                },
18599                |_row, _folded, _window, _cx| div(),
18600            );
18601
18602            editor.insert_creases(Some(crease), cx);
18603            let snapshot = editor.snapshot(window, cx);
18604            let _div = snapshot.render_crease_toggle(
18605                MultiBufferRow(1),
18606                false,
18607                cx.entity().clone(),
18608                window,
18609                cx,
18610            );
18611            snapshot
18612        })
18613        .unwrap();
18614
18615    let render_args = render_args.lock().take().unwrap();
18616    assert_eq!(render_args.row, MultiBufferRow(1));
18617    assert!(!render_args.folded);
18618    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18619
18620    cx.update_window(*editor, |_, window, cx| {
18621        (render_args.callback)(true, window, cx)
18622    })
18623    .unwrap();
18624    let snapshot = editor
18625        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18626        .unwrap();
18627    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18628
18629    cx.update_window(*editor, |_, window, cx| {
18630        (render_args.callback)(false, window, cx)
18631    })
18632    .unwrap();
18633    let snapshot = editor
18634        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18635        .unwrap();
18636    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18637}
18638
18639#[gpui::test]
18640async fn test_input_text(cx: &mut TestAppContext) {
18641    init_test(cx, |_| {});
18642    let mut cx = EditorTestContext::new(cx).await;
18643
18644    cx.set_state(
18645        &r#"ˇone
18646        two
18647
18648        three
18649        fourˇ
18650        five
18651
18652        siˇx"#
18653            .unindent(),
18654    );
18655
18656    cx.dispatch_action(HandleInput(String::new()));
18657    cx.assert_editor_state(
18658        &r#"ˇone
18659        two
18660
18661        three
18662        fourˇ
18663        five
18664
18665        siˇx"#
18666            .unindent(),
18667    );
18668
18669    cx.dispatch_action(HandleInput("AAAA".to_string()));
18670    cx.assert_editor_state(
18671        &r#"AAAAˇone
18672        two
18673
18674        three
18675        fourAAAAˇ
18676        five
18677
18678        siAAAAˇx"#
18679            .unindent(),
18680    );
18681}
18682
18683#[gpui::test]
18684async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18685    init_test(cx, |_| {});
18686
18687    let mut cx = EditorTestContext::new(cx).await;
18688    cx.set_state(
18689        r#"let foo = 1;
18690let foo = 2;
18691let foo = 3;
18692let fooˇ = 4;
18693let foo = 5;
18694let foo = 6;
18695let foo = 7;
18696let foo = 8;
18697let foo = 9;
18698let foo = 10;
18699let foo = 11;
18700let foo = 12;
18701let foo = 13;
18702let foo = 14;
18703let foo = 15;"#,
18704    );
18705
18706    cx.update_editor(|e, window, cx| {
18707        assert_eq!(
18708            e.next_scroll_position,
18709            NextScrollCursorCenterTopBottom::Center,
18710            "Default next scroll direction is center",
18711        );
18712
18713        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18714        assert_eq!(
18715            e.next_scroll_position,
18716            NextScrollCursorCenterTopBottom::Top,
18717            "After center, next scroll direction should be top",
18718        );
18719
18720        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18721        assert_eq!(
18722            e.next_scroll_position,
18723            NextScrollCursorCenterTopBottom::Bottom,
18724            "After top, next scroll direction should be bottom",
18725        );
18726
18727        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18728        assert_eq!(
18729            e.next_scroll_position,
18730            NextScrollCursorCenterTopBottom::Center,
18731            "After bottom, scrolling should start over",
18732        );
18733
18734        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
18735        assert_eq!(
18736            e.next_scroll_position,
18737            NextScrollCursorCenterTopBottom::Top,
18738            "Scrolling continues if retriggered fast enough"
18739        );
18740    });
18741
18742    cx.executor()
18743        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
18744    cx.executor().run_until_parked();
18745    cx.update_editor(|e, _, _| {
18746        assert_eq!(
18747            e.next_scroll_position,
18748            NextScrollCursorCenterTopBottom::Center,
18749            "If scrolling is not triggered fast enough, it should reset"
18750        );
18751    });
18752}
18753
18754#[gpui::test]
18755async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
18756    init_test(cx, |_| {});
18757    let mut cx = EditorLspTestContext::new_rust(
18758        lsp::ServerCapabilities {
18759            definition_provider: Some(lsp::OneOf::Left(true)),
18760            references_provider: Some(lsp::OneOf::Left(true)),
18761            ..lsp::ServerCapabilities::default()
18762        },
18763        cx,
18764    )
18765    .await;
18766
18767    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
18768        let go_to_definition = cx
18769            .lsp
18770            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18771                move |params, _| async move {
18772                    if empty_go_to_definition {
18773                        Ok(None)
18774                    } else {
18775                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
18776                            uri: params.text_document_position_params.text_document.uri,
18777                            range: lsp::Range::new(
18778                                lsp::Position::new(4, 3),
18779                                lsp::Position::new(4, 6),
18780                            ),
18781                        })))
18782                    }
18783                },
18784            );
18785        let references = cx
18786            .lsp
18787            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
18788                Ok(Some(vec![lsp::Location {
18789                    uri: params.text_document_position.text_document.uri,
18790                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
18791                }]))
18792            });
18793        (go_to_definition, references)
18794    };
18795
18796    cx.set_state(
18797        &r#"fn one() {
18798            let mut a = ˇtwo();
18799        }
18800
18801        fn two() {}"#
18802            .unindent(),
18803    );
18804    set_up_lsp_handlers(false, &mut cx);
18805    let navigated = cx
18806        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18807        .await
18808        .expect("Failed to navigate to definition");
18809    assert_eq!(
18810        navigated,
18811        Navigated::Yes,
18812        "Should have navigated to definition from the GetDefinition response"
18813    );
18814    cx.assert_editor_state(
18815        &r#"fn one() {
18816            let mut a = two();
18817        }
18818
18819        fn «twoˇ»() {}"#
18820            .unindent(),
18821    );
18822
18823    let editors = cx.update_workspace(|workspace, _, cx| {
18824        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18825    });
18826    cx.update_editor(|_, _, test_editor_cx| {
18827        assert_eq!(
18828            editors.len(),
18829            1,
18830            "Initially, only one, test, editor should be open in the workspace"
18831        );
18832        assert_eq!(
18833            test_editor_cx.entity(),
18834            editors.last().expect("Asserted len is 1").clone()
18835        );
18836    });
18837
18838    set_up_lsp_handlers(true, &mut cx);
18839    let navigated = cx
18840        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18841        .await
18842        .expect("Failed to navigate to lookup references");
18843    assert_eq!(
18844        navigated,
18845        Navigated::Yes,
18846        "Should have navigated to references as a fallback after empty GoToDefinition response"
18847    );
18848    // We should not change the selections in the existing file,
18849    // if opening another milti buffer with the references
18850    cx.assert_editor_state(
18851        &r#"fn one() {
18852            let mut a = two();
18853        }
18854
18855        fn «twoˇ»() {}"#
18856            .unindent(),
18857    );
18858    let editors = cx.update_workspace(|workspace, _, cx| {
18859        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18860    });
18861    cx.update_editor(|_, _, test_editor_cx| {
18862        assert_eq!(
18863            editors.len(),
18864            2,
18865            "After falling back to references search, we open a new editor with the results"
18866        );
18867        let references_fallback_text = editors
18868            .into_iter()
18869            .find(|new_editor| *new_editor != test_editor_cx.entity())
18870            .expect("Should have one non-test editor now")
18871            .read(test_editor_cx)
18872            .text(test_editor_cx);
18873        assert_eq!(
18874            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
18875            "Should use the range from the references response and not the GoToDefinition one"
18876        );
18877    });
18878}
18879
18880#[gpui::test]
18881async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
18882    init_test(cx, |_| {});
18883    cx.update(|cx| {
18884        let mut editor_settings = EditorSettings::get_global(cx).clone();
18885        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
18886        EditorSettings::override_global(editor_settings, cx);
18887    });
18888    let mut cx = EditorLspTestContext::new_rust(
18889        lsp::ServerCapabilities {
18890            definition_provider: Some(lsp::OneOf::Left(true)),
18891            references_provider: Some(lsp::OneOf::Left(true)),
18892            ..lsp::ServerCapabilities::default()
18893        },
18894        cx,
18895    )
18896    .await;
18897    let original_state = r#"fn one() {
18898        let mut a = ˇtwo();
18899    }
18900
18901    fn two() {}"#
18902        .unindent();
18903    cx.set_state(&original_state);
18904
18905    let mut go_to_definition = cx
18906        .lsp
18907        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
18908            move |_, _| async move { Ok(None) },
18909        );
18910    let _references = cx
18911        .lsp
18912        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
18913            panic!("Should not call for references with no go to definition fallback")
18914        });
18915
18916    let navigated = cx
18917        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
18918        .await
18919        .expect("Failed to navigate to lookup references");
18920    go_to_definition
18921        .next()
18922        .await
18923        .expect("Should have called the go_to_definition handler");
18924
18925    assert_eq!(
18926        navigated,
18927        Navigated::No,
18928        "Should have navigated to references as a fallback after empty GoToDefinition response"
18929    );
18930    cx.assert_editor_state(&original_state);
18931    let editors = cx.update_workspace(|workspace, _, cx| {
18932        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
18933    });
18934    cx.update_editor(|_, _, _| {
18935        assert_eq!(
18936            editors.len(),
18937            1,
18938            "After unsuccessful fallback, no other editor should have been opened"
18939        );
18940    });
18941}
18942
18943#[gpui::test]
18944async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
18945    init_test(cx, |_| {});
18946
18947    let language = Arc::new(Language::new(
18948        LanguageConfig::default(),
18949        Some(tree_sitter_rust::LANGUAGE.into()),
18950    ));
18951
18952    let text = r#"
18953        #[cfg(test)]
18954        mod tests() {
18955            #[test]
18956            fn runnable_1() {
18957                let a = 1;
18958            }
18959
18960            #[test]
18961            fn runnable_2() {
18962                let a = 1;
18963                let b = 2;
18964            }
18965        }
18966    "#
18967    .unindent();
18968
18969    let fs = FakeFs::new(cx.executor());
18970    fs.insert_file("/file.rs", Default::default()).await;
18971
18972    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18973    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18974    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18975    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18976    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
18977
18978    let editor = cx.new_window_entity(|window, cx| {
18979        Editor::new(
18980            EditorMode::full(),
18981            multi_buffer,
18982            Some(project.clone()),
18983            window,
18984            cx,
18985        )
18986    });
18987
18988    editor.update_in(cx, |editor, window, cx| {
18989        let snapshot = editor.buffer().read(cx).snapshot(cx);
18990        editor.tasks.insert(
18991            (buffer.read(cx).remote_id(), 3),
18992            RunnableTasks {
18993                templates: vec![],
18994                offset: snapshot.anchor_before(43),
18995                column: 0,
18996                extra_variables: HashMap::default(),
18997                context_range: BufferOffset(43)..BufferOffset(85),
18998            },
18999        );
19000        editor.tasks.insert(
19001            (buffer.read(cx).remote_id(), 8),
19002            RunnableTasks {
19003                templates: vec![],
19004                offset: snapshot.anchor_before(86),
19005                column: 0,
19006                extra_variables: HashMap::default(),
19007                context_range: BufferOffset(86)..BufferOffset(191),
19008            },
19009        );
19010
19011        // Test finding task when cursor is inside function body
19012        editor.change_selections(None, window, cx, |s| {
19013            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19014        });
19015        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19016        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19017
19018        // Test finding task when cursor is on function name
19019        editor.change_selections(None, window, cx, |s| {
19020            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19021        });
19022        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19023        assert_eq!(row, 8, "Should find task when cursor is on function name");
19024    });
19025}
19026
19027#[gpui::test]
19028async fn test_folding_buffers(cx: &mut TestAppContext) {
19029    init_test(cx, |_| {});
19030
19031    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19032    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19033    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19034
19035    let fs = FakeFs::new(cx.executor());
19036    fs.insert_tree(
19037        path!("/a"),
19038        json!({
19039            "first.rs": sample_text_1,
19040            "second.rs": sample_text_2,
19041            "third.rs": sample_text_3,
19042        }),
19043    )
19044    .await;
19045    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19046    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19047    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19048    let worktree = project.update(cx, |project, cx| {
19049        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19050        assert_eq!(worktrees.len(), 1);
19051        worktrees.pop().unwrap()
19052    });
19053    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19054
19055    let buffer_1 = project
19056        .update(cx, |project, cx| {
19057            project.open_buffer((worktree_id, "first.rs"), cx)
19058        })
19059        .await
19060        .unwrap();
19061    let buffer_2 = project
19062        .update(cx, |project, cx| {
19063            project.open_buffer((worktree_id, "second.rs"), cx)
19064        })
19065        .await
19066        .unwrap();
19067    let buffer_3 = project
19068        .update(cx, |project, cx| {
19069            project.open_buffer((worktree_id, "third.rs"), cx)
19070        })
19071        .await
19072        .unwrap();
19073
19074    let multi_buffer = cx.new(|cx| {
19075        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19076        multi_buffer.push_excerpts(
19077            buffer_1.clone(),
19078            [
19079                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19080                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19081                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19082            ],
19083            cx,
19084        );
19085        multi_buffer.push_excerpts(
19086            buffer_2.clone(),
19087            [
19088                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19089                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19090                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19091            ],
19092            cx,
19093        );
19094        multi_buffer.push_excerpts(
19095            buffer_3.clone(),
19096            [
19097                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19098                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19099                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19100            ],
19101            cx,
19102        );
19103        multi_buffer
19104    });
19105    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19106        Editor::new(
19107            EditorMode::full(),
19108            multi_buffer.clone(),
19109            Some(project.clone()),
19110            window,
19111            cx,
19112        )
19113    });
19114
19115    assert_eq!(
19116        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19117        "\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",
19118    );
19119
19120    multi_buffer_editor.update(cx, |editor, cx| {
19121        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19122    });
19123    assert_eq!(
19124        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19125        "\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",
19126        "After folding the first buffer, its text should not be displayed"
19127    );
19128
19129    multi_buffer_editor.update(cx, |editor, cx| {
19130        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19131    });
19132    assert_eq!(
19133        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19134        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19135        "After folding the second buffer, its text should not be displayed"
19136    );
19137
19138    multi_buffer_editor.update(cx, |editor, cx| {
19139        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19140    });
19141    assert_eq!(
19142        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19143        "\n\n\n\n\n",
19144        "After folding the third buffer, its text should not be displayed"
19145    );
19146
19147    // Emulate selection inside the fold logic, that should work
19148    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19149        editor
19150            .snapshot(window, cx)
19151            .next_line_boundary(Point::new(0, 4));
19152    });
19153
19154    multi_buffer_editor.update(cx, |editor, cx| {
19155        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19156    });
19157    assert_eq!(
19158        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19159        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19160        "After unfolding the second buffer, its text should be displayed"
19161    );
19162
19163    // Typing inside of buffer 1 causes that buffer to be unfolded.
19164    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19165        assert_eq!(
19166            multi_buffer
19167                .read(cx)
19168                .snapshot(cx)
19169                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19170                .collect::<String>(),
19171            "bbbb"
19172        );
19173        editor.change_selections(None, window, cx, |selections| {
19174            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19175        });
19176        editor.handle_input("B", window, cx);
19177    });
19178
19179    assert_eq!(
19180        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19181        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19182        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19183    );
19184
19185    multi_buffer_editor.update(cx, |editor, cx| {
19186        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19187    });
19188    assert_eq!(
19189        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19190        "\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",
19191        "After unfolding the all buffers, all original text should be displayed"
19192    );
19193}
19194
19195#[gpui::test]
19196async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19197    init_test(cx, |_| {});
19198
19199    let sample_text_1 = "1111\n2222\n3333".to_string();
19200    let sample_text_2 = "4444\n5555\n6666".to_string();
19201    let sample_text_3 = "7777\n8888\n9999".to_string();
19202
19203    let fs = FakeFs::new(cx.executor());
19204    fs.insert_tree(
19205        path!("/a"),
19206        json!({
19207            "first.rs": sample_text_1,
19208            "second.rs": sample_text_2,
19209            "third.rs": sample_text_3,
19210        }),
19211    )
19212    .await;
19213    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19214    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19215    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19216    let worktree = project.update(cx, |project, cx| {
19217        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19218        assert_eq!(worktrees.len(), 1);
19219        worktrees.pop().unwrap()
19220    });
19221    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19222
19223    let buffer_1 = project
19224        .update(cx, |project, cx| {
19225            project.open_buffer((worktree_id, "first.rs"), cx)
19226        })
19227        .await
19228        .unwrap();
19229    let buffer_2 = project
19230        .update(cx, |project, cx| {
19231            project.open_buffer((worktree_id, "second.rs"), cx)
19232        })
19233        .await
19234        .unwrap();
19235    let buffer_3 = project
19236        .update(cx, |project, cx| {
19237            project.open_buffer((worktree_id, "third.rs"), cx)
19238        })
19239        .await
19240        .unwrap();
19241
19242    let multi_buffer = cx.new(|cx| {
19243        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19244        multi_buffer.push_excerpts(
19245            buffer_1.clone(),
19246            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19247            cx,
19248        );
19249        multi_buffer.push_excerpts(
19250            buffer_2.clone(),
19251            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19252            cx,
19253        );
19254        multi_buffer.push_excerpts(
19255            buffer_3.clone(),
19256            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19257            cx,
19258        );
19259        multi_buffer
19260    });
19261
19262    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19263        Editor::new(
19264            EditorMode::full(),
19265            multi_buffer,
19266            Some(project.clone()),
19267            window,
19268            cx,
19269        )
19270    });
19271
19272    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19273    assert_eq!(
19274        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19275        full_text,
19276    );
19277
19278    multi_buffer_editor.update(cx, |editor, cx| {
19279        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19280    });
19281    assert_eq!(
19282        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19283        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19284        "After folding the first buffer, its text should not be displayed"
19285    );
19286
19287    multi_buffer_editor.update(cx, |editor, cx| {
19288        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19289    });
19290
19291    assert_eq!(
19292        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19293        "\n\n\n\n\n\n7777\n8888\n9999",
19294        "After folding the second buffer, its text should not be displayed"
19295    );
19296
19297    multi_buffer_editor.update(cx, |editor, cx| {
19298        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19299    });
19300    assert_eq!(
19301        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19302        "\n\n\n\n\n",
19303        "After folding the third buffer, its text should not be displayed"
19304    );
19305
19306    multi_buffer_editor.update(cx, |editor, cx| {
19307        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19308    });
19309    assert_eq!(
19310        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19311        "\n\n\n\n4444\n5555\n6666\n\n",
19312        "After unfolding the second buffer, its text should be displayed"
19313    );
19314
19315    multi_buffer_editor.update(cx, |editor, cx| {
19316        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19317    });
19318    assert_eq!(
19319        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19320        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19321        "After unfolding the first buffer, its text should be displayed"
19322    );
19323
19324    multi_buffer_editor.update(cx, |editor, cx| {
19325        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19326    });
19327    assert_eq!(
19328        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19329        full_text,
19330        "After unfolding all buffers, all original text should be displayed"
19331    );
19332}
19333
19334#[gpui::test]
19335async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19336    init_test(cx, |_| {});
19337
19338    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19339
19340    let fs = FakeFs::new(cx.executor());
19341    fs.insert_tree(
19342        path!("/a"),
19343        json!({
19344            "main.rs": sample_text,
19345        }),
19346    )
19347    .await;
19348    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19349    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19350    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19351    let worktree = project.update(cx, |project, cx| {
19352        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19353        assert_eq!(worktrees.len(), 1);
19354        worktrees.pop().unwrap()
19355    });
19356    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19357
19358    let buffer_1 = project
19359        .update(cx, |project, cx| {
19360            project.open_buffer((worktree_id, "main.rs"), cx)
19361        })
19362        .await
19363        .unwrap();
19364
19365    let multi_buffer = cx.new(|cx| {
19366        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19367        multi_buffer.push_excerpts(
19368            buffer_1.clone(),
19369            [ExcerptRange::new(
19370                Point::new(0, 0)
19371                    ..Point::new(
19372                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19373                        0,
19374                    ),
19375            )],
19376            cx,
19377        );
19378        multi_buffer
19379    });
19380    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19381        Editor::new(
19382            EditorMode::full(),
19383            multi_buffer,
19384            Some(project.clone()),
19385            window,
19386            cx,
19387        )
19388    });
19389
19390    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19391    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19392        enum TestHighlight {}
19393        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19394        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19395        editor.highlight_text::<TestHighlight>(
19396            vec![highlight_range.clone()],
19397            HighlightStyle::color(Hsla::green()),
19398            cx,
19399        );
19400        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
19401    });
19402
19403    let full_text = format!("\n\n{sample_text}");
19404    assert_eq!(
19405        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19406        full_text,
19407    );
19408}
19409
19410#[gpui::test]
19411async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19412    init_test(cx, |_| {});
19413    cx.update(|cx| {
19414        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19415            "keymaps/default-linux.json",
19416            cx,
19417        )
19418        .unwrap();
19419        cx.bind_keys(default_key_bindings);
19420    });
19421
19422    let (editor, cx) = cx.add_window_view(|window, cx| {
19423        let multi_buffer = MultiBuffer::build_multi(
19424            [
19425                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19426                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19427                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19428                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19429            ],
19430            cx,
19431        );
19432        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19433
19434        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19435        // fold all but the second buffer, so that we test navigating between two
19436        // adjacent folded buffers, as well as folded buffers at the start and
19437        // end the multibuffer
19438        editor.fold_buffer(buffer_ids[0], cx);
19439        editor.fold_buffer(buffer_ids[2], cx);
19440        editor.fold_buffer(buffer_ids[3], cx);
19441
19442        editor
19443    });
19444    cx.simulate_resize(size(px(1000.), px(1000.)));
19445
19446    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19447    cx.assert_excerpts_with_selections(indoc! {"
19448        [EXCERPT]
19449        ˇ[FOLDED]
19450        [EXCERPT]
19451        a1
19452        b1
19453        [EXCERPT]
19454        [FOLDED]
19455        [EXCERPT]
19456        [FOLDED]
19457        "
19458    });
19459    cx.simulate_keystroke("down");
19460    cx.assert_excerpts_with_selections(indoc! {"
19461        [EXCERPT]
19462        [FOLDED]
19463        [EXCERPT]
19464        ˇa1
19465        b1
19466        [EXCERPT]
19467        [FOLDED]
19468        [EXCERPT]
19469        [FOLDED]
19470        "
19471    });
19472    cx.simulate_keystroke("down");
19473    cx.assert_excerpts_with_selections(indoc! {"
19474        [EXCERPT]
19475        [FOLDED]
19476        [EXCERPT]
19477        a1
19478        ˇb1
19479        [EXCERPT]
19480        [FOLDED]
19481        [EXCERPT]
19482        [FOLDED]
19483        "
19484    });
19485    cx.simulate_keystroke("down");
19486    cx.assert_excerpts_with_selections(indoc! {"
19487        [EXCERPT]
19488        [FOLDED]
19489        [EXCERPT]
19490        a1
19491        b1
19492        ˇ[EXCERPT]
19493        [FOLDED]
19494        [EXCERPT]
19495        [FOLDED]
19496        "
19497    });
19498    cx.simulate_keystroke("down");
19499    cx.assert_excerpts_with_selections(indoc! {"
19500        [EXCERPT]
19501        [FOLDED]
19502        [EXCERPT]
19503        a1
19504        b1
19505        [EXCERPT]
19506        ˇ[FOLDED]
19507        [EXCERPT]
19508        [FOLDED]
19509        "
19510    });
19511    for _ in 0..5 {
19512        cx.simulate_keystroke("down");
19513        cx.assert_excerpts_with_selections(indoc! {"
19514            [EXCERPT]
19515            [FOLDED]
19516            [EXCERPT]
19517            a1
19518            b1
19519            [EXCERPT]
19520            [FOLDED]
19521            [EXCERPT]
19522            ˇ[FOLDED]
19523            "
19524        });
19525    }
19526
19527    cx.simulate_keystroke("up");
19528    cx.assert_excerpts_with_selections(indoc! {"
19529        [EXCERPT]
19530        [FOLDED]
19531        [EXCERPT]
19532        a1
19533        b1
19534        [EXCERPT]
19535        ˇ[FOLDED]
19536        [EXCERPT]
19537        [FOLDED]
19538        "
19539    });
19540    cx.simulate_keystroke("up");
19541    cx.assert_excerpts_with_selections(indoc! {"
19542        [EXCERPT]
19543        [FOLDED]
19544        [EXCERPT]
19545        a1
19546        b1
19547        ˇ[EXCERPT]
19548        [FOLDED]
19549        [EXCERPT]
19550        [FOLDED]
19551        "
19552    });
19553    cx.simulate_keystroke("up");
19554    cx.assert_excerpts_with_selections(indoc! {"
19555        [EXCERPT]
19556        [FOLDED]
19557        [EXCERPT]
19558        a1
19559        ˇb1
19560        [EXCERPT]
19561        [FOLDED]
19562        [EXCERPT]
19563        [FOLDED]
19564        "
19565    });
19566    cx.simulate_keystroke("up");
19567    cx.assert_excerpts_with_selections(indoc! {"
19568        [EXCERPT]
19569        [FOLDED]
19570        [EXCERPT]
19571        ˇa1
19572        b1
19573        [EXCERPT]
19574        [FOLDED]
19575        [EXCERPT]
19576        [FOLDED]
19577        "
19578    });
19579    for _ in 0..5 {
19580        cx.simulate_keystroke("up");
19581        cx.assert_excerpts_with_selections(indoc! {"
19582            [EXCERPT]
19583            ˇ[FOLDED]
19584            [EXCERPT]
19585            a1
19586            b1
19587            [EXCERPT]
19588            [FOLDED]
19589            [EXCERPT]
19590            [FOLDED]
19591            "
19592        });
19593    }
19594}
19595
19596#[gpui::test]
19597async fn test_inline_completion_text(cx: &mut TestAppContext) {
19598    init_test(cx, |_| {});
19599
19600    // Simple insertion
19601    assert_highlighted_edits(
19602        "Hello, world!",
19603        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19604        true,
19605        cx,
19606        |highlighted_edits, cx| {
19607            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19608            assert_eq!(highlighted_edits.highlights.len(), 1);
19609            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19610            assert_eq!(
19611                highlighted_edits.highlights[0].1.background_color,
19612                Some(cx.theme().status().created_background)
19613            );
19614        },
19615    )
19616    .await;
19617
19618    // Replacement
19619    assert_highlighted_edits(
19620        "This is a test.",
19621        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19622        false,
19623        cx,
19624        |highlighted_edits, cx| {
19625            assert_eq!(highlighted_edits.text, "That is a test.");
19626            assert_eq!(highlighted_edits.highlights.len(), 1);
19627            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19628            assert_eq!(
19629                highlighted_edits.highlights[0].1.background_color,
19630                Some(cx.theme().status().created_background)
19631            );
19632        },
19633    )
19634    .await;
19635
19636    // Multiple edits
19637    assert_highlighted_edits(
19638        "Hello, world!",
19639        vec![
19640            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19641            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19642        ],
19643        false,
19644        cx,
19645        |highlighted_edits, cx| {
19646            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19647            assert_eq!(highlighted_edits.highlights.len(), 2);
19648            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19649            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19650            assert_eq!(
19651                highlighted_edits.highlights[0].1.background_color,
19652                Some(cx.theme().status().created_background)
19653            );
19654            assert_eq!(
19655                highlighted_edits.highlights[1].1.background_color,
19656                Some(cx.theme().status().created_background)
19657            );
19658        },
19659    )
19660    .await;
19661
19662    // Multiple lines with edits
19663    assert_highlighted_edits(
19664        "First line\nSecond line\nThird line\nFourth line",
19665        vec![
19666            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19667            (
19668                Point::new(2, 0)..Point::new(2, 10),
19669                "New third line".to_string(),
19670            ),
19671            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19672        ],
19673        false,
19674        cx,
19675        |highlighted_edits, cx| {
19676            assert_eq!(
19677                highlighted_edits.text,
19678                "Second modified\nNew third line\nFourth updated line"
19679            );
19680            assert_eq!(highlighted_edits.highlights.len(), 3);
19681            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19682            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19683            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19684            for highlight in &highlighted_edits.highlights {
19685                assert_eq!(
19686                    highlight.1.background_color,
19687                    Some(cx.theme().status().created_background)
19688                );
19689            }
19690        },
19691    )
19692    .await;
19693}
19694
19695#[gpui::test]
19696async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19697    init_test(cx, |_| {});
19698
19699    // Deletion
19700    assert_highlighted_edits(
19701        "Hello, world!",
19702        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
19703        true,
19704        cx,
19705        |highlighted_edits, cx| {
19706            assert_eq!(highlighted_edits.text, "Hello, world!");
19707            assert_eq!(highlighted_edits.highlights.len(), 1);
19708            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
19709            assert_eq!(
19710                highlighted_edits.highlights[0].1.background_color,
19711                Some(cx.theme().status().deleted_background)
19712            );
19713        },
19714    )
19715    .await;
19716
19717    // Insertion
19718    assert_highlighted_edits(
19719        "Hello, world!",
19720        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
19721        true,
19722        cx,
19723        |highlighted_edits, cx| {
19724            assert_eq!(highlighted_edits.highlights.len(), 1);
19725            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
19726            assert_eq!(
19727                highlighted_edits.highlights[0].1.background_color,
19728                Some(cx.theme().status().created_background)
19729            );
19730        },
19731    )
19732    .await;
19733}
19734
19735async fn assert_highlighted_edits(
19736    text: &str,
19737    edits: Vec<(Range<Point>, String)>,
19738    include_deletions: bool,
19739    cx: &mut TestAppContext,
19740    assertion_fn: impl Fn(HighlightedText, &App),
19741) {
19742    let window = cx.add_window(|window, cx| {
19743        let buffer = MultiBuffer::build_simple(text, cx);
19744        Editor::new(EditorMode::full(), buffer, None, window, cx)
19745    });
19746    let cx = &mut VisualTestContext::from_window(*window, cx);
19747
19748    let (buffer, snapshot) = window
19749        .update(cx, |editor, _window, cx| {
19750            (
19751                editor.buffer().clone(),
19752                editor.buffer().read(cx).snapshot(cx),
19753            )
19754        })
19755        .unwrap();
19756
19757    let edits = edits
19758        .into_iter()
19759        .map(|(range, edit)| {
19760            (
19761                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
19762                edit,
19763            )
19764        })
19765        .collect::<Vec<_>>();
19766
19767    let text_anchor_edits = edits
19768        .clone()
19769        .into_iter()
19770        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
19771        .collect::<Vec<_>>();
19772
19773    let edit_preview = window
19774        .update(cx, |_, _window, cx| {
19775            buffer
19776                .read(cx)
19777                .as_singleton()
19778                .unwrap()
19779                .read(cx)
19780                .preview_edits(text_anchor_edits.into(), cx)
19781        })
19782        .unwrap()
19783        .await;
19784
19785    cx.update(|_window, cx| {
19786        let highlighted_edits = inline_completion_edit_text(
19787            &snapshot.as_singleton().unwrap().2,
19788            &edits,
19789            &edit_preview,
19790            include_deletions,
19791            cx,
19792        );
19793        assertion_fn(highlighted_edits, cx)
19794    });
19795}
19796
19797#[track_caller]
19798fn assert_breakpoint(
19799    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
19800    path: &Arc<Path>,
19801    expected: Vec<(u32, Breakpoint)>,
19802) {
19803    if expected.len() == 0usize {
19804        assert!(!breakpoints.contains_key(path), "{}", path.display());
19805    } else {
19806        let mut breakpoint = breakpoints
19807            .get(path)
19808            .unwrap()
19809            .into_iter()
19810            .map(|breakpoint| {
19811                (
19812                    breakpoint.row,
19813                    Breakpoint {
19814                        message: breakpoint.message.clone(),
19815                        state: breakpoint.state,
19816                        condition: breakpoint.condition.clone(),
19817                        hit_condition: breakpoint.hit_condition.clone(),
19818                    },
19819                )
19820            })
19821            .collect::<Vec<_>>();
19822
19823        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
19824
19825        assert_eq!(expected, breakpoint);
19826    }
19827}
19828
19829fn add_log_breakpoint_at_cursor(
19830    editor: &mut Editor,
19831    log_message: &str,
19832    window: &mut Window,
19833    cx: &mut Context<Editor>,
19834) {
19835    let (anchor, bp) = editor
19836        .breakpoints_at_cursors(window, cx)
19837        .first()
19838        .and_then(|(anchor, bp)| {
19839            if let Some(bp) = bp {
19840                Some((*anchor, bp.clone()))
19841            } else {
19842                None
19843            }
19844        })
19845        .unwrap_or_else(|| {
19846            let cursor_position: Point = editor.selections.newest(cx).head();
19847
19848            let breakpoint_position = editor
19849                .snapshot(window, cx)
19850                .display_snapshot
19851                .buffer_snapshot
19852                .anchor_before(Point::new(cursor_position.row, 0));
19853
19854            (breakpoint_position, Breakpoint::new_log(&log_message))
19855        });
19856
19857    editor.edit_breakpoint_at_anchor(
19858        anchor,
19859        bp,
19860        BreakpointEditAction::EditLogMessage(log_message.into()),
19861        cx,
19862    );
19863}
19864
19865#[gpui::test]
19866async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
19867    init_test(cx, |_| {});
19868
19869    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19870    let fs = FakeFs::new(cx.executor());
19871    fs.insert_tree(
19872        path!("/a"),
19873        json!({
19874            "main.rs": sample_text,
19875        }),
19876    )
19877    .await;
19878    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19879    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19880    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19881
19882    let fs = FakeFs::new(cx.executor());
19883    fs.insert_tree(
19884        path!("/a"),
19885        json!({
19886            "main.rs": sample_text,
19887        }),
19888    )
19889    .await;
19890    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19891    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19892    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19893    let worktree_id = workspace
19894        .update(cx, |workspace, _window, cx| {
19895            workspace.project().update(cx, |project, cx| {
19896                project.worktrees(cx).next().unwrap().read(cx).id()
19897            })
19898        })
19899        .unwrap();
19900
19901    let buffer = project
19902        .update(cx, |project, cx| {
19903            project.open_buffer((worktree_id, "main.rs"), cx)
19904        })
19905        .await
19906        .unwrap();
19907
19908    let (editor, cx) = cx.add_window_view(|window, cx| {
19909        Editor::new(
19910            EditorMode::full(),
19911            MultiBuffer::build_from_buffer(buffer, cx),
19912            Some(project.clone()),
19913            window,
19914            cx,
19915        )
19916    });
19917
19918    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
19919    let abs_path = project.read_with(cx, |project, cx| {
19920        project
19921            .absolute_path(&project_path, cx)
19922            .map(|path_buf| Arc::from(path_buf.to_owned()))
19923            .unwrap()
19924    });
19925
19926    // assert we can add breakpoint on the first line
19927    editor.update_in(cx, |editor, window, cx| {
19928        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19929        editor.move_to_end(&MoveToEnd, window, cx);
19930        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19931    });
19932
19933    let breakpoints = editor.update(cx, |editor, cx| {
19934        editor
19935            .breakpoint_store()
19936            .as_ref()
19937            .unwrap()
19938            .read(cx)
19939            .all_source_breakpoints(cx)
19940            .clone()
19941    });
19942
19943    assert_eq!(1, breakpoints.len());
19944    assert_breakpoint(
19945        &breakpoints,
19946        &abs_path,
19947        vec![
19948            (0, Breakpoint::new_standard()),
19949            (3, Breakpoint::new_standard()),
19950        ],
19951    );
19952
19953    editor.update_in(cx, |editor, window, cx| {
19954        editor.move_to_beginning(&MoveToBeginning, window, cx);
19955        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19956    });
19957
19958    let breakpoints = editor.update(cx, |editor, cx| {
19959        editor
19960            .breakpoint_store()
19961            .as_ref()
19962            .unwrap()
19963            .read(cx)
19964            .all_source_breakpoints(cx)
19965            .clone()
19966    });
19967
19968    assert_eq!(1, breakpoints.len());
19969    assert_breakpoint(
19970        &breakpoints,
19971        &abs_path,
19972        vec![(3, Breakpoint::new_standard())],
19973    );
19974
19975    editor.update_in(cx, |editor, window, cx| {
19976        editor.move_to_end(&MoveToEnd, window, cx);
19977        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
19978    });
19979
19980    let breakpoints = editor.update(cx, |editor, cx| {
19981        editor
19982            .breakpoint_store()
19983            .as_ref()
19984            .unwrap()
19985            .read(cx)
19986            .all_source_breakpoints(cx)
19987            .clone()
19988    });
19989
19990    assert_eq!(0, breakpoints.len());
19991    assert_breakpoint(&breakpoints, &abs_path, vec![]);
19992}
19993
19994#[gpui::test]
19995async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
19996    init_test(cx, |_| {});
19997
19998    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
19999
20000    let fs = FakeFs::new(cx.executor());
20001    fs.insert_tree(
20002        path!("/a"),
20003        json!({
20004            "main.rs": sample_text,
20005        }),
20006    )
20007    .await;
20008    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20009    let (workspace, cx) =
20010        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20011
20012    let worktree_id = workspace.update(cx, |workspace, cx| {
20013        workspace.project().update(cx, |project, cx| {
20014            project.worktrees(cx).next().unwrap().read(cx).id()
20015        })
20016    });
20017
20018    let buffer = project
20019        .update(cx, |project, cx| {
20020            project.open_buffer((worktree_id, "main.rs"), cx)
20021        })
20022        .await
20023        .unwrap();
20024
20025    let (editor, cx) = cx.add_window_view(|window, cx| {
20026        Editor::new(
20027            EditorMode::full(),
20028            MultiBuffer::build_from_buffer(buffer, cx),
20029            Some(project.clone()),
20030            window,
20031            cx,
20032        )
20033    });
20034
20035    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20036    let abs_path = project.read_with(cx, |project, cx| {
20037        project
20038            .absolute_path(&project_path, cx)
20039            .map(|path_buf| Arc::from(path_buf.to_owned()))
20040            .unwrap()
20041    });
20042
20043    editor.update_in(cx, |editor, window, cx| {
20044        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20045    });
20046
20047    let breakpoints = editor.update(cx, |editor, cx| {
20048        editor
20049            .breakpoint_store()
20050            .as_ref()
20051            .unwrap()
20052            .read(cx)
20053            .all_source_breakpoints(cx)
20054            .clone()
20055    });
20056
20057    assert_breakpoint(
20058        &breakpoints,
20059        &abs_path,
20060        vec![(0, Breakpoint::new_log("hello world"))],
20061    );
20062
20063    // Removing a log message from a log breakpoint should remove it
20064    editor.update_in(cx, |editor, window, cx| {
20065        add_log_breakpoint_at_cursor(editor, "", window, cx);
20066    });
20067
20068    let breakpoints = editor.update(cx, |editor, cx| {
20069        editor
20070            .breakpoint_store()
20071            .as_ref()
20072            .unwrap()
20073            .read(cx)
20074            .all_source_breakpoints(cx)
20075            .clone()
20076    });
20077
20078    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20079
20080    editor.update_in(cx, |editor, window, cx| {
20081        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20082        editor.move_to_end(&MoveToEnd, window, cx);
20083        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20084        // Not adding a log message to a standard breakpoint shouldn't remove it
20085        add_log_breakpoint_at_cursor(editor, "", window, cx);
20086    });
20087
20088    let breakpoints = editor.update(cx, |editor, cx| {
20089        editor
20090            .breakpoint_store()
20091            .as_ref()
20092            .unwrap()
20093            .read(cx)
20094            .all_source_breakpoints(cx)
20095            .clone()
20096    });
20097
20098    assert_breakpoint(
20099        &breakpoints,
20100        &abs_path,
20101        vec![
20102            (0, Breakpoint::new_standard()),
20103            (3, Breakpoint::new_standard()),
20104        ],
20105    );
20106
20107    editor.update_in(cx, |editor, window, cx| {
20108        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20109    });
20110
20111    let breakpoints = editor.update(cx, |editor, cx| {
20112        editor
20113            .breakpoint_store()
20114            .as_ref()
20115            .unwrap()
20116            .read(cx)
20117            .all_source_breakpoints(cx)
20118            .clone()
20119    });
20120
20121    assert_breakpoint(
20122        &breakpoints,
20123        &abs_path,
20124        vec![
20125            (0, Breakpoint::new_standard()),
20126            (3, Breakpoint::new_log("hello world")),
20127        ],
20128    );
20129
20130    editor.update_in(cx, |editor, window, cx| {
20131        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20132    });
20133
20134    let breakpoints = editor.update(cx, |editor, cx| {
20135        editor
20136            .breakpoint_store()
20137            .as_ref()
20138            .unwrap()
20139            .read(cx)
20140            .all_source_breakpoints(cx)
20141            .clone()
20142    });
20143
20144    assert_breakpoint(
20145        &breakpoints,
20146        &abs_path,
20147        vec![
20148            (0, Breakpoint::new_standard()),
20149            (3, Breakpoint::new_log("hello Earth!!")),
20150        ],
20151    );
20152}
20153
20154/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20155/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20156/// or when breakpoints were placed out of order. This tests for a regression too
20157#[gpui::test]
20158async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20159    init_test(cx, |_| {});
20160
20161    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20162    let fs = FakeFs::new(cx.executor());
20163    fs.insert_tree(
20164        path!("/a"),
20165        json!({
20166            "main.rs": sample_text,
20167        }),
20168    )
20169    .await;
20170    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20171    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20172    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20173
20174    let fs = FakeFs::new(cx.executor());
20175    fs.insert_tree(
20176        path!("/a"),
20177        json!({
20178            "main.rs": sample_text,
20179        }),
20180    )
20181    .await;
20182    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20183    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20184    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20185    let worktree_id = workspace
20186        .update(cx, |workspace, _window, cx| {
20187            workspace.project().update(cx, |project, cx| {
20188                project.worktrees(cx).next().unwrap().read(cx).id()
20189            })
20190        })
20191        .unwrap();
20192
20193    let buffer = project
20194        .update(cx, |project, cx| {
20195            project.open_buffer((worktree_id, "main.rs"), cx)
20196        })
20197        .await
20198        .unwrap();
20199
20200    let (editor, cx) = cx.add_window_view(|window, cx| {
20201        Editor::new(
20202            EditorMode::full(),
20203            MultiBuffer::build_from_buffer(buffer, cx),
20204            Some(project.clone()),
20205            window,
20206            cx,
20207        )
20208    });
20209
20210    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20211    let abs_path = project.read_with(cx, |project, cx| {
20212        project
20213            .absolute_path(&project_path, cx)
20214            .map(|path_buf| Arc::from(path_buf.to_owned()))
20215            .unwrap()
20216    });
20217
20218    // assert we can add breakpoint on the first line
20219    editor.update_in(cx, |editor, window, cx| {
20220        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20221        editor.move_to_end(&MoveToEnd, window, cx);
20222        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20223        editor.move_up(&MoveUp, window, cx);
20224        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20225    });
20226
20227    let breakpoints = editor.update(cx, |editor, cx| {
20228        editor
20229            .breakpoint_store()
20230            .as_ref()
20231            .unwrap()
20232            .read(cx)
20233            .all_source_breakpoints(cx)
20234            .clone()
20235    });
20236
20237    assert_eq!(1, breakpoints.len());
20238    assert_breakpoint(
20239        &breakpoints,
20240        &abs_path,
20241        vec![
20242            (0, Breakpoint::new_standard()),
20243            (2, Breakpoint::new_standard()),
20244            (3, Breakpoint::new_standard()),
20245        ],
20246    );
20247
20248    editor.update_in(cx, |editor, window, cx| {
20249        editor.move_to_beginning(&MoveToBeginning, window, cx);
20250        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20251        editor.move_to_end(&MoveToEnd, window, cx);
20252        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20253        // Disabling a breakpoint that doesn't exist should do nothing
20254        editor.move_up(&MoveUp, window, cx);
20255        editor.move_up(&MoveUp, window, cx);
20256        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20257    });
20258
20259    let breakpoints = editor.update(cx, |editor, cx| {
20260        editor
20261            .breakpoint_store()
20262            .as_ref()
20263            .unwrap()
20264            .read(cx)
20265            .all_source_breakpoints(cx)
20266            .clone()
20267    });
20268
20269    let disable_breakpoint = {
20270        let mut bp = Breakpoint::new_standard();
20271        bp.state = BreakpointState::Disabled;
20272        bp
20273    };
20274
20275    assert_eq!(1, breakpoints.len());
20276    assert_breakpoint(
20277        &breakpoints,
20278        &abs_path,
20279        vec![
20280            (0, disable_breakpoint.clone()),
20281            (2, Breakpoint::new_standard()),
20282            (3, disable_breakpoint.clone()),
20283        ],
20284    );
20285
20286    editor.update_in(cx, |editor, window, cx| {
20287        editor.move_to_beginning(&MoveToBeginning, window, cx);
20288        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20289        editor.move_to_end(&MoveToEnd, window, cx);
20290        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20291        editor.move_up(&MoveUp, window, cx);
20292        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20293    });
20294
20295    let breakpoints = editor.update(cx, |editor, cx| {
20296        editor
20297            .breakpoint_store()
20298            .as_ref()
20299            .unwrap()
20300            .read(cx)
20301            .all_source_breakpoints(cx)
20302            .clone()
20303    });
20304
20305    assert_eq!(1, breakpoints.len());
20306    assert_breakpoint(
20307        &breakpoints,
20308        &abs_path,
20309        vec![
20310            (0, Breakpoint::new_standard()),
20311            (2, disable_breakpoint),
20312            (3, Breakpoint::new_standard()),
20313        ],
20314    );
20315}
20316
20317#[gpui::test]
20318async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20319    init_test(cx, |_| {});
20320    let capabilities = lsp::ServerCapabilities {
20321        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20322            prepare_provider: Some(true),
20323            work_done_progress_options: Default::default(),
20324        })),
20325        ..Default::default()
20326    };
20327    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20328
20329    cx.set_state(indoc! {"
20330        struct Fˇoo {}
20331    "});
20332
20333    cx.update_editor(|editor, _, cx| {
20334        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20335        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20336        editor.highlight_background::<DocumentHighlightRead>(
20337            &[highlight_range],
20338            |theme| theme.colors().editor_document_highlight_read_background,
20339            cx,
20340        );
20341    });
20342
20343    let mut prepare_rename_handler = cx
20344        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20345            move |_, _, _| async move {
20346                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20347                    start: lsp::Position {
20348                        line: 0,
20349                        character: 7,
20350                    },
20351                    end: lsp::Position {
20352                        line: 0,
20353                        character: 10,
20354                    },
20355                })))
20356            },
20357        );
20358    let prepare_rename_task = cx
20359        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20360        .expect("Prepare rename was not started");
20361    prepare_rename_handler.next().await.unwrap();
20362    prepare_rename_task.await.expect("Prepare rename failed");
20363
20364    let mut rename_handler =
20365        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20366            let edit = lsp::TextEdit {
20367                range: lsp::Range {
20368                    start: lsp::Position {
20369                        line: 0,
20370                        character: 7,
20371                    },
20372                    end: lsp::Position {
20373                        line: 0,
20374                        character: 10,
20375                    },
20376                },
20377                new_text: "FooRenamed".to_string(),
20378            };
20379            Ok(Some(lsp::WorkspaceEdit::new(
20380                // Specify the same edit twice
20381                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20382            )))
20383        });
20384    let rename_task = cx
20385        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20386        .expect("Confirm rename was not started");
20387    rename_handler.next().await.unwrap();
20388    rename_task.await.expect("Confirm rename failed");
20389    cx.run_until_parked();
20390
20391    // Despite two edits, only one is actually applied as those are identical
20392    cx.assert_editor_state(indoc! {"
20393        struct FooRenamedˇ {}
20394    "});
20395}
20396
20397#[gpui::test]
20398async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20399    init_test(cx, |_| {});
20400    // These capabilities indicate that the server does not support prepare rename.
20401    let capabilities = lsp::ServerCapabilities {
20402        rename_provider: Some(lsp::OneOf::Left(true)),
20403        ..Default::default()
20404    };
20405    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20406
20407    cx.set_state(indoc! {"
20408        struct Fˇoo {}
20409    "});
20410
20411    cx.update_editor(|editor, _window, cx| {
20412        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20413        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20414        editor.highlight_background::<DocumentHighlightRead>(
20415            &[highlight_range],
20416            |theme| theme.colors().editor_document_highlight_read_background,
20417            cx,
20418        );
20419    });
20420
20421    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20422        .expect("Prepare rename was not started")
20423        .await
20424        .expect("Prepare rename failed");
20425
20426    let mut rename_handler =
20427        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20428            let edit = lsp::TextEdit {
20429                range: lsp::Range {
20430                    start: lsp::Position {
20431                        line: 0,
20432                        character: 7,
20433                    },
20434                    end: lsp::Position {
20435                        line: 0,
20436                        character: 10,
20437                    },
20438                },
20439                new_text: "FooRenamed".to_string(),
20440            };
20441            Ok(Some(lsp::WorkspaceEdit::new(
20442                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20443            )))
20444        });
20445    let rename_task = cx
20446        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20447        .expect("Confirm rename was not started");
20448    rename_handler.next().await.unwrap();
20449    rename_task.await.expect("Confirm rename failed");
20450    cx.run_until_parked();
20451
20452    // Correct range is renamed, as `surrounding_word` is used to find it.
20453    cx.assert_editor_state(indoc! {"
20454        struct FooRenamedˇ {}
20455    "});
20456}
20457
20458#[gpui::test]
20459async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20460    init_test(cx, |_| {});
20461    let mut cx = EditorTestContext::new(cx).await;
20462
20463    let language = Arc::new(
20464        Language::new(
20465            LanguageConfig::default(),
20466            Some(tree_sitter_html::LANGUAGE.into()),
20467        )
20468        .with_brackets_query(
20469            r#"
20470            ("<" @open "/>" @close)
20471            ("</" @open ">" @close)
20472            ("<" @open ">" @close)
20473            ("\"" @open "\"" @close)
20474            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20475        "#,
20476        )
20477        .unwrap(),
20478    );
20479    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20480
20481    cx.set_state(indoc! {"
20482        <span>ˇ</span>
20483    "});
20484    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20485    cx.assert_editor_state(indoc! {"
20486        <span>
20487        ˇ
20488        </span>
20489    "});
20490
20491    cx.set_state(indoc! {"
20492        <span><span></span>ˇ</span>
20493    "});
20494    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20495    cx.assert_editor_state(indoc! {"
20496        <span><span></span>
20497        ˇ</span>
20498    "});
20499
20500    cx.set_state(indoc! {"
20501        <span>ˇ
20502        </span>
20503    "});
20504    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20505    cx.assert_editor_state(indoc! {"
20506        <span>
20507        ˇ
20508        </span>
20509    "});
20510}
20511
20512#[gpui::test(iterations = 10)]
20513async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20514    init_test(cx, |_| {});
20515
20516    let fs = FakeFs::new(cx.executor());
20517    fs.insert_tree(
20518        path!("/dir"),
20519        json!({
20520            "a.ts": "a",
20521        }),
20522    )
20523    .await;
20524
20525    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20526    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20527    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20528
20529    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20530    language_registry.add(Arc::new(Language::new(
20531        LanguageConfig {
20532            name: "TypeScript".into(),
20533            matcher: LanguageMatcher {
20534                path_suffixes: vec!["ts".to_string()],
20535                ..Default::default()
20536            },
20537            ..Default::default()
20538        },
20539        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20540    )));
20541    let mut fake_language_servers = language_registry.register_fake_lsp(
20542        "TypeScript",
20543        FakeLspAdapter {
20544            capabilities: lsp::ServerCapabilities {
20545                code_lens_provider: Some(lsp::CodeLensOptions {
20546                    resolve_provider: Some(true),
20547                }),
20548                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20549                    commands: vec!["_the/command".to_string()],
20550                    ..lsp::ExecuteCommandOptions::default()
20551                }),
20552                ..lsp::ServerCapabilities::default()
20553            },
20554            ..FakeLspAdapter::default()
20555        },
20556    );
20557
20558    let (buffer, _handle) = project
20559        .update(cx, |p, cx| {
20560            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20561        })
20562        .await
20563        .unwrap();
20564    cx.executor().run_until_parked();
20565
20566    let fake_server = fake_language_servers.next().await.unwrap();
20567
20568    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20569    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20570    drop(buffer_snapshot);
20571    let actions = cx
20572        .update_window(*workspace, |_, window, cx| {
20573            project.code_actions(&buffer, anchor..anchor, window, cx)
20574        })
20575        .unwrap();
20576
20577    fake_server
20578        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20579            Ok(Some(vec![
20580                lsp::CodeLens {
20581                    range: lsp::Range::default(),
20582                    command: Some(lsp::Command {
20583                        title: "Code lens command".to_owned(),
20584                        command: "_the/command".to_owned(),
20585                        arguments: None,
20586                    }),
20587                    data: None,
20588                },
20589                lsp::CodeLens {
20590                    range: lsp::Range::default(),
20591                    command: Some(lsp::Command {
20592                        title: "Command not in capabilities".to_owned(),
20593                        command: "not in capabilities".to_owned(),
20594                        arguments: None,
20595                    }),
20596                    data: None,
20597                },
20598                lsp::CodeLens {
20599                    range: lsp::Range {
20600                        start: lsp::Position {
20601                            line: 1,
20602                            character: 1,
20603                        },
20604                        end: lsp::Position {
20605                            line: 1,
20606                            character: 1,
20607                        },
20608                    },
20609                    command: Some(lsp::Command {
20610                        title: "Command not in range".to_owned(),
20611                        command: "_the/command".to_owned(),
20612                        arguments: None,
20613                    }),
20614                    data: None,
20615                },
20616            ]))
20617        })
20618        .next()
20619        .await;
20620
20621    let actions = actions.await.unwrap();
20622    assert_eq!(
20623        actions.len(),
20624        1,
20625        "Should have only one valid action for the 0..0 range"
20626    );
20627    let action = actions[0].clone();
20628    let apply = project.update(cx, |project, cx| {
20629        project.apply_code_action(buffer.clone(), action, true, cx)
20630    });
20631
20632    // Resolving the code action does not populate its edits. In absence of
20633    // edits, we must execute the given command.
20634    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20635        |mut lens, _| async move {
20636            let lens_command = lens.command.as_mut().expect("should have a command");
20637            assert_eq!(lens_command.title, "Code lens command");
20638            lens_command.arguments = Some(vec![json!("the-argument")]);
20639            Ok(lens)
20640        },
20641    );
20642
20643    // While executing the command, the language server sends the editor
20644    // a `workspaceEdit` request.
20645    fake_server
20646        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20647            let fake = fake_server.clone();
20648            move |params, _| {
20649                assert_eq!(params.command, "_the/command");
20650                let fake = fake.clone();
20651                async move {
20652                    fake.server
20653                        .request::<lsp::request::ApplyWorkspaceEdit>(
20654                            lsp::ApplyWorkspaceEditParams {
20655                                label: None,
20656                                edit: lsp::WorkspaceEdit {
20657                                    changes: Some(
20658                                        [(
20659                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20660                                            vec![lsp::TextEdit {
20661                                                range: lsp::Range::new(
20662                                                    lsp::Position::new(0, 0),
20663                                                    lsp::Position::new(0, 0),
20664                                                ),
20665                                                new_text: "X".into(),
20666                                            }],
20667                                        )]
20668                                        .into_iter()
20669                                        .collect(),
20670                                    ),
20671                                    ..Default::default()
20672                                },
20673                            },
20674                        )
20675                        .await
20676                        .into_response()
20677                        .unwrap();
20678                    Ok(Some(json!(null)))
20679                }
20680            }
20681        })
20682        .next()
20683        .await;
20684
20685    // Applying the code lens command returns a project transaction containing the edits
20686    // sent by the language server in its `workspaceEdit` request.
20687    let transaction = apply.await.unwrap();
20688    assert!(transaction.0.contains_key(&buffer));
20689    buffer.update(cx, |buffer, cx| {
20690        assert_eq!(buffer.text(), "Xa");
20691        buffer.undo(cx);
20692        assert_eq!(buffer.text(), "a");
20693    });
20694}
20695
20696#[gpui::test]
20697async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20698    init_test(cx, |_| {});
20699
20700    let fs = FakeFs::new(cx.executor());
20701    let main_text = r#"fn main() {
20702println!("1");
20703println!("2");
20704println!("3");
20705println!("4");
20706println!("5");
20707}"#;
20708    let lib_text = "mod foo {}";
20709    fs.insert_tree(
20710        path!("/a"),
20711        json!({
20712            "lib.rs": lib_text,
20713            "main.rs": main_text,
20714        }),
20715    )
20716    .await;
20717
20718    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20719    let (workspace, cx) =
20720        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20721    let worktree_id = workspace.update(cx, |workspace, cx| {
20722        workspace.project().update(cx, |project, cx| {
20723            project.worktrees(cx).next().unwrap().read(cx).id()
20724        })
20725    });
20726
20727    let expected_ranges = vec![
20728        Point::new(0, 0)..Point::new(0, 0),
20729        Point::new(1, 0)..Point::new(1, 1),
20730        Point::new(2, 0)..Point::new(2, 2),
20731        Point::new(3, 0)..Point::new(3, 3),
20732    ];
20733
20734    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20735    let editor_1 = workspace
20736        .update_in(cx, |workspace, window, cx| {
20737            workspace.open_path(
20738                (worktree_id, "main.rs"),
20739                Some(pane_1.downgrade()),
20740                true,
20741                window,
20742                cx,
20743            )
20744        })
20745        .unwrap()
20746        .await
20747        .downcast::<Editor>()
20748        .unwrap();
20749    pane_1.update(cx, |pane, cx| {
20750        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20751        open_editor.update(cx, |editor, cx| {
20752            assert_eq!(
20753                editor.display_text(cx),
20754                main_text,
20755                "Original main.rs text on initial open",
20756            );
20757            assert_eq!(
20758                editor
20759                    .selections
20760                    .all::<Point>(cx)
20761                    .into_iter()
20762                    .map(|s| s.range())
20763                    .collect::<Vec<_>>(),
20764                vec![Point::zero()..Point::zero()],
20765                "Default selections on initial open",
20766            );
20767        })
20768    });
20769    editor_1.update_in(cx, |editor, window, cx| {
20770        editor.change_selections(None, window, cx, |s| {
20771            s.select_ranges(expected_ranges.clone());
20772        });
20773    });
20774
20775    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
20776        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
20777    });
20778    let editor_2 = workspace
20779        .update_in(cx, |workspace, window, cx| {
20780            workspace.open_path(
20781                (worktree_id, "main.rs"),
20782                Some(pane_2.downgrade()),
20783                true,
20784                window,
20785                cx,
20786            )
20787        })
20788        .unwrap()
20789        .await
20790        .downcast::<Editor>()
20791        .unwrap();
20792    pane_2.update(cx, |pane, cx| {
20793        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20794        open_editor.update(cx, |editor, cx| {
20795            assert_eq!(
20796                editor.display_text(cx),
20797                main_text,
20798                "Original main.rs text on initial open in another panel",
20799            );
20800            assert_eq!(
20801                editor
20802                    .selections
20803                    .all::<Point>(cx)
20804                    .into_iter()
20805                    .map(|s| s.range())
20806                    .collect::<Vec<_>>(),
20807                vec![Point::zero()..Point::zero()],
20808                "Default selections on initial open in another panel",
20809            );
20810        })
20811    });
20812
20813    editor_2.update_in(cx, |editor, window, cx| {
20814        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
20815    });
20816
20817    let _other_editor_1 = workspace
20818        .update_in(cx, |workspace, window, cx| {
20819            workspace.open_path(
20820                (worktree_id, "lib.rs"),
20821                Some(pane_1.downgrade()),
20822                true,
20823                window,
20824                cx,
20825            )
20826        })
20827        .unwrap()
20828        .await
20829        .downcast::<Editor>()
20830        .unwrap();
20831    pane_1
20832        .update_in(cx, |pane, window, cx| {
20833            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20834        })
20835        .await
20836        .unwrap();
20837    drop(editor_1);
20838    pane_1.update(cx, |pane, cx| {
20839        pane.active_item()
20840            .unwrap()
20841            .downcast::<Editor>()
20842            .unwrap()
20843            .update(cx, |editor, cx| {
20844                assert_eq!(
20845                    editor.display_text(cx),
20846                    lib_text,
20847                    "Other file should be open and active",
20848                );
20849            });
20850        assert_eq!(pane.items().count(), 1, "No other editors should be open");
20851    });
20852
20853    let _other_editor_2 = workspace
20854        .update_in(cx, |workspace, window, cx| {
20855            workspace.open_path(
20856                (worktree_id, "lib.rs"),
20857                Some(pane_2.downgrade()),
20858                true,
20859                window,
20860                cx,
20861            )
20862        })
20863        .unwrap()
20864        .await
20865        .downcast::<Editor>()
20866        .unwrap();
20867    pane_2
20868        .update_in(cx, |pane, window, cx| {
20869            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
20870        })
20871        .await
20872        .unwrap();
20873    drop(editor_2);
20874    pane_2.update(cx, |pane, cx| {
20875        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20876        open_editor.update(cx, |editor, cx| {
20877            assert_eq!(
20878                editor.display_text(cx),
20879                lib_text,
20880                "Other file should be open and active in another panel too",
20881            );
20882        });
20883        assert_eq!(
20884            pane.items().count(),
20885            1,
20886            "No other editors should be open in another pane",
20887        );
20888    });
20889
20890    let _editor_1_reopened = workspace
20891        .update_in(cx, |workspace, window, cx| {
20892            workspace.open_path(
20893                (worktree_id, "main.rs"),
20894                Some(pane_1.downgrade()),
20895                true,
20896                window,
20897                cx,
20898            )
20899        })
20900        .unwrap()
20901        .await
20902        .downcast::<Editor>()
20903        .unwrap();
20904    let _editor_2_reopened = workspace
20905        .update_in(cx, |workspace, window, cx| {
20906            workspace.open_path(
20907                (worktree_id, "main.rs"),
20908                Some(pane_2.downgrade()),
20909                true,
20910                window,
20911                cx,
20912            )
20913        })
20914        .unwrap()
20915        .await
20916        .downcast::<Editor>()
20917        .unwrap();
20918    pane_1.update(cx, |pane, cx| {
20919        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20920        open_editor.update(cx, |editor, cx| {
20921            assert_eq!(
20922                editor.display_text(cx),
20923                main_text,
20924                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
20925            );
20926            assert_eq!(
20927                editor
20928                    .selections
20929                    .all::<Point>(cx)
20930                    .into_iter()
20931                    .map(|s| s.range())
20932                    .collect::<Vec<_>>(),
20933                expected_ranges,
20934                "Previous editor in the 1st panel had selections and should get them restored on reopen",
20935            );
20936        })
20937    });
20938    pane_2.update(cx, |pane, cx| {
20939        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
20940        open_editor.update(cx, |editor, cx| {
20941            assert_eq!(
20942                editor.display_text(cx),
20943                r#"fn main() {
20944⋯rintln!("1");
20945⋯intln!("2");
20946⋯ntln!("3");
20947println!("4");
20948println!("5");
20949}"#,
20950                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
20951            );
20952            assert_eq!(
20953                editor
20954                    .selections
20955                    .all::<Point>(cx)
20956                    .into_iter()
20957                    .map(|s| s.range())
20958                    .collect::<Vec<_>>(),
20959                vec![Point::zero()..Point::zero()],
20960                "Previous editor in the 2nd pane had no selections changed hence should restore none",
20961            );
20962        })
20963    });
20964}
20965
20966#[gpui::test]
20967async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
20968    init_test(cx, |_| {});
20969
20970    let fs = FakeFs::new(cx.executor());
20971    let main_text = r#"fn main() {
20972println!("1");
20973println!("2");
20974println!("3");
20975println!("4");
20976println!("5");
20977}"#;
20978    let lib_text = "mod foo {}";
20979    fs.insert_tree(
20980        path!("/a"),
20981        json!({
20982            "lib.rs": lib_text,
20983            "main.rs": main_text,
20984        }),
20985    )
20986    .await;
20987
20988    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20989    let (workspace, cx) =
20990        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20991    let worktree_id = workspace.update(cx, |workspace, cx| {
20992        workspace.project().update(cx, |project, cx| {
20993            project.worktrees(cx).next().unwrap().read(cx).id()
20994        })
20995    });
20996
20997    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
20998    let editor = workspace
20999        .update_in(cx, |workspace, window, cx| {
21000            workspace.open_path(
21001                (worktree_id, "main.rs"),
21002                Some(pane.downgrade()),
21003                true,
21004                window,
21005                cx,
21006            )
21007        })
21008        .unwrap()
21009        .await
21010        .downcast::<Editor>()
21011        .unwrap();
21012    pane.update(cx, |pane, cx| {
21013        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21014        open_editor.update(cx, |editor, cx| {
21015            assert_eq!(
21016                editor.display_text(cx),
21017                main_text,
21018                "Original main.rs text on initial open",
21019            );
21020        })
21021    });
21022    editor.update_in(cx, |editor, window, cx| {
21023        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21024    });
21025
21026    cx.update_global(|store: &mut SettingsStore, cx| {
21027        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21028            s.restore_on_file_reopen = Some(false);
21029        });
21030    });
21031    editor.update_in(cx, |editor, window, cx| {
21032        editor.fold_ranges(
21033            vec![
21034                Point::new(1, 0)..Point::new(1, 1),
21035                Point::new(2, 0)..Point::new(2, 2),
21036                Point::new(3, 0)..Point::new(3, 3),
21037            ],
21038            false,
21039            window,
21040            cx,
21041        );
21042    });
21043    pane.update_in(cx, |pane, window, cx| {
21044        pane.close_all_items(&CloseAllItems::default(), window, cx)
21045    })
21046    .await
21047    .unwrap();
21048    pane.update(cx, |pane, _| {
21049        assert!(pane.active_item().is_none());
21050    });
21051    cx.update_global(|store: &mut SettingsStore, cx| {
21052        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21053            s.restore_on_file_reopen = Some(true);
21054        });
21055    });
21056
21057    let _editor_reopened = workspace
21058        .update_in(cx, |workspace, window, cx| {
21059            workspace.open_path(
21060                (worktree_id, "main.rs"),
21061                Some(pane.downgrade()),
21062                true,
21063                window,
21064                cx,
21065            )
21066        })
21067        .unwrap()
21068        .await
21069        .downcast::<Editor>()
21070        .unwrap();
21071    pane.update(cx, |pane, cx| {
21072        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21073        open_editor.update(cx, |editor, cx| {
21074            assert_eq!(
21075                editor.display_text(cx),
21076                main_text,
21077                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21078            );
21079        })
21080    });
21081}
21082
21083#[gpui::test]
21084async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21085    struct EmptyModalView {
21086        focus_handle: gpui::FocusHandle,
21087    }
21088    impl EventEmitter<DismissEvent> for EmptyModalView {}
21089    impl Render for EmptyModalView {
21090        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21091            div()
21092        }
21093    }
21094    impl Focusable for EmptyModalView {
21095        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21096            self.focus_handle.clone()
21097        }
21098    }
21099    impl workspace::ModalView for EmptyModalView {}
21100    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21101        EmptyModalView {
21102            focus_handle: cx.focus_handle(),
21103        }
21104    }
21105
21106    init_test(cx, |_| {});
21107
21108    let fs = FakeFs::new(cx.executor());
21109    let project = Project::test(fs, [], cx).await;
21110    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21111    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21112    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21113    let editor = cx.new_window_entity(|window, cx| {
21114        Editor::new(
21115            EditorMode::full(),
21116            buffer,
21117            Some(project.clone()),
21118            window,
21119            cx,
21120        )
21121    });
21122    workspace
21123        .update(cx, |workspace, window, cx| {
21124            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21125        })
21126        .unwrap();
21127    editor.update_in(cx, |editor, window, cx| {
21128        editor.open_context_menu(&OpenContextMenu, window, cx);
21129        assert!(editor.mouse_context_menu.is_some());
21130    });
21131    workspace
21132        .update(cx, |workspace, window, cx| {
21133            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21134        })
21135        .unwrap();
21136    cx.read(|cx| {
21137        assert!(editor.read(cx).mouse_context_menu.is_none());
21138    });
21139}
21140
21141#[gpui::test]
21142async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21143    init_test(cx, |_| {});
21144
21145    let fs = FakeFs::new(cx.executor());
21146    fs.insert_file(path!("/file.html"), Default::default())
21147        .await;
21148
21149    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21150
21151    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21152    let html_language = Arc::new(Language::new(
21153        LanguageConfig {
21154            name: "HTML".into(),
21155            matcher: LanguageMatcher {
21156                path_suffixes: vec!["html".to_string()],
21157                ..LanguageMatcher::default()
21158            },
21159            brackets: BracketPairConfig {
21160                pairs: vec![BracketPair {
21161                    start: "<".into(),
21162                    end: ">".into(),
21163                    close: true,
21164                    ..Default::default()
21165                }],
21166                ..Default::default()
21167            },
21168            ..Default::default()
21169        },
21170        Some(tree_sitter_html::LANGUAGE.into()),
21171    ));
21172    language_registry.add(html_language);
21173    let mut fake_servers = language_registry.register_fake_lsp(
21174        "HTML",
21175        FakeLspAdapter {
21176            capabilities: lsp::ServerCapabilities {
21177                completion_provider: Some(lsp::CompletionOptions {
21178                    resolve_provider: Some(true),
21179                    ..Default::default()
21180                }),
21181                ..Default::default()
21182            },
21183            ..Default::default()
21184        },
21185    );
21186
21187    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21188    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21189
21190    let worktree_id = workspace
21191        .update(cx, |workspace, _window, cx| {
21192            workspace.project().update(cx, |project, cx| {
21193                project.worktrees(cx).next().unwrap().read(cx).id()
21194            })
21195        })
21196        .unwrap();
21197    project
21198        .update(cx, |project, cx| {
21199            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21200        })
21201        .await
21202        .unwrap();
21203    let editor = workspace
21204        .update(cx, |workspace, window, cx| {
21205            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21206        })
21207        .unwrap()
21208        .await
21209        .unwrap()
21210        .downcast::<Editor>()
21211        .unwrap();
21212
21213    let fake_server = fake_servers.next().await.unwrap();
21214    editor.update_in(cx, |editor, window, cx| {
21215        editor.set_text("<ad></ad>", window, cx);
21216        editor.change_selections(None, window, cx, |selections| {
21217            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21218        });
21219        let Some((buffer, _)) = editor
21220            .buffer
21221            .read(cx)
21222            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21223        else {
21224            panic!("Failed to get buffer for selection position");
21225        };
21226        let buffer = buffer.read(cx);
21227        let buffer_id = buffer.remote_id();
21228        let opening_range =
21229            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21230        let closing_range =
21231            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21232        let mut linked_ranges = HashMap::default();
21233        linked_ranges.insert(
21234            buffer_id,
21235            vec![(opening_range.clone(), vec![closing_range.clone()])],
21236        );
21237        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21238    });
21239    let mut completion_handle =
21240        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21241            Ok(Some(lsp::CompletionResponse::Array(vec![
21242                lsp::CompletionItem {
21243                    label: "head".to_string(),
21244                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21245                        lsp::InsertReplaceEdit {
21246                            new_text: "head".to_string(),
21247                            insert: lsp::Range::new(
21248                                lsp::Position::new(0, 1),
21249                                lsp::Position::new(0, 3),
21250                            ),
21251                            replace: lsp::Range::new(
21252                                lsp::Position::new(0, 1),
21253                                lsp::Position::new(0, 3),
21254                            ),
21255                        },
21256                    )),
21257                    ..Default::default()
21258                },
21259            ])))
21260        });
21261    editor.update_in(cx, |editor, window, cx| {
21262        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21263    });
21264    cx.run_until_parked();
21265    completion_handle.next().await.unwrap();
21266    editor.update(cx, |editor, _| {
21267        assert!(
21268            editor.context_menu_visible(),
21269            "Completion menu should be visible"
21270        );
21271    });
21272    editor.update_in(cx, |editor, window, cx| {
21273        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21274    });
21275    cx.executor().run_until_parked();
21276    editor.update(cx, |editor, cx| {
21277        assert_eq!(editor.text(cx), "<head></head>");
21278    });
21279}
21280
21281#[gpui::test]
21282async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21283    init_test(cx, |_| {});
21284
21285    let fs = FakeFs::new(cx.executor());
21286    fs.insert_tree(
21287        path!("/root"),
21288        json!({
21289            "a": {
21290                "main.rs": "fn main() {}",
21291            },
21292            "foo": {
21293                "bar": {
21294                    "external_file.rs": "pub mod external {}",
21295                }
21296            }
21297        }),
21298    )
21299    .await;
21300
21301    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21302    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21303    language_registry.add(rust_lang());
21304    let _fake_servers = language_registry.register_fake_lsp(
21305        "Rust",
21306        FakeLspAdapter {
21307            ..FakeLspAdapter::default()
21308        },
21309    );
21310    let (workspace, cx) =
21311        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21312    let worktree_id = workspace.update(cx, |workspace, cx| {
21313        workspace.project().update(cx, |project, cx| {
21314            project.worktrees(cx).next().unwrap().read(cx).id()
21315        })
21316    });
21317
21318    let assert_language_servers_count =
21319        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21320            project.update(cx, |project, cx| {
21321                let current = project
21322                    .lsp_store()
21323                    .read(cx)
21324                    .as_local()
21325                    .unwrap()
21326                    .language_servers
21327                    .len();
21328                assert_eq!(expected, current, "{context}");
21329            });
21330        };
21331
21332    assert_language_servers_count(
21333        0,
21334        "No servers should be running before any file is open",
21335        cx,
21336    );
21337    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21338    let main_editor = workspace
21339        .update_in(cx, |workspace, window, cx| {
21340            workspace.open_path(
21341                (worktree_id, "main.rs"),
21342                Some(pane.downgrade()),
21343                true,
21344                window,
21345                cx,
21346            )
21347        })
21348        .unwrap()
21349        .await
21350        .downcast::<Editor>()
21351        .unwrap();
21352    pane.update(cx, |pane, cx| {
21353        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21354        open_editor.update(cx, |editor, cx| {
21355            assert_eq!(
21356                editor.display_text(cx),
21357                "fn main() {}",
21358                "Original main.rs text on initial open",
21359            );
21360        });
21361        assert_eq!(open_editor, main_editor);
21362    });
21363    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21364
21365    let external_editor = workspace
21366        .update_in(cx, |workspace, window, cx| {
21367            workspace.open_abs_path(
21368                PathBuf::from("/root/foo/bar/external_file.rs"),
21369                OpenOptions::default(),
21370                window,
21371                cx,
21372            )
21373        })
21374        .await
21375        .expect("opening external file")
21376        .downcast::<Editor>()
21377        .expect("downcasted external file's open element to editor");
21378    pane.update(cx, |pane, cx| {
21379        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21380        open_editor.update(cx, |editor, cx| {
21381            assert_eq!(
21382                editor.display_text(cx),
21383                "pub mod external {}",
21384                "External file is open now",
21385            );
21386        });
21387        assert_eq!(open_editor, external_editor);
21388    });
21389    assert_language_servers_count(
21390        1,
21391        "Second, external, *.rs file should join the existing server",
21392        cx,
21393    );
21394
21395    pane.update_in(cx, |pane, window, cx| {
21396        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21397    })
21398    .await
21399    .unwrap();
21400    pane.update_in(cx, |pane, window, cx| {
21401        pane.navigate_backward(window, cx);
21402    });
21403    cx.run_until_parked();
21404    pane.update(cx, |pane, cx| {
21405        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21406        open_editor.update(cx, |editor, cx| {
21407            assert_eq!(
21408                editor.display_text(cx),
21409                "pub mod external {}",
21410                "External file is open now",
21411            );
21412        });
21413    });
21414    assert_language_servers_count(
21415        1,
21416        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21417        cx,
21418    );
21419
21420    cx.update(|_, cx| {
21421        workspace::reload(&workspace::Reload::default(), cx);
21422    });
21423    assert_language_servers_count(
21424        1,
21425        "After reloading the worktree with local and external files opened, only one project should be started",
21426        cx,
21427    );
21428}
21429
21430#[gpui::test]
21431async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21432    init_test(cx, |_| {});
21433
21434    let mut cx = EditorTestContext::new(cx).await;
21435    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21436    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21437
21438    // test cursor move to start of each line on tab
21439    // for `if`, `elif`, `else`, `while`, `with` and `for`
21440    cx.set_state(indoc! {"
21441        def main():
21442        ˇ    for item in items:
21443        ˇ        while item.active:
21444        ˇ            if item.value > 10:
21445        ˇ                continue
21446        ˇ            elif item.value < 0:
21447        ˇ                break
21448        ˇ            else:
21449        ˇ                with item.context() as ctx:
21450        ˇ                    yield count
21451        ˇ        else:
21452        ˇ            log('while else')
21453        ˇ    else:
21454        ˇ        log('for else')
21455    "});
21456    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21457    cx.assert_editor_state(indoc! {"
21458        def main():
21459            ˇfor item in items:
21460                ˇwhile item.active:
21461                    ˇif item.value > 10:
21462                        ˇcontinue
21463                    ˇelif item.value < 0:
21464                        ˇbreak
21465                    ˇelse:
21466                        ˇwith item.context() as ctx:
21467                            ˇyield count
21468                ˇelse:
21469                    ˇlog('while else')
21470            ˇelse:
21471                ˇlog('for else')
21472    "});
21473    // test relative indent is preserved when tab
21474    // for `if`, `elif`, `else`, `while`, `with` and `for`
21475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21476    cx.assert_editor_state(indoc! {"
21477        def main():
21478                ˇfor item in items:
21479                    ˇwhile item.active:
21480                        ˇif item.value > 10:
21481                            ˇcontinue
21482                        ˇelif item.value < 0:
21483                            ˇbreak
21484                        ˇelse:
21485                            ˇwith item.context() as ctx:
21486                                ˇyield count
21487                    ˇelse:
21488                        ˇlog('while else')
21489                ˇelse:
21490                    ˇlog('for else')
21491    "});
21492
21493    // test cursor move to start of each line on tab
21494    // for `try`, `except`, `else`, `finally`, `match` and `def`
21495    cx.set_state(indoc! {"
21496        def main():
21497        ˇ    try:
21498        ˇ       fetch()
21499        ˇ    except ValueError:
21500        ˇ       handle_error()
21501        ˇ    else:
21502        ˇ        match value:
21503        ˇ            case _:
21504        ˇ    finally:
21505        ˇ        def status():
21506        ˇ            return 0
21507    "});
21508    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21509    cx.assert_editor_state(indoc! {"
21510        def main():
21511            ˇtry:
21512                ˇfetch()
21513            ˇexcept ValueError:
21514                ˇhandle_error()
21515            ˇelse:
21516                ˇmatch value:
21517                    ˇcase _:
21518            ˇfinally:
21519                ˇdef status():
21520                    ˇreturn 0
21521    "});
21522    // test relative indent is preserved when tab
21523    // for `try`, `except`, `else`, `finally`, `match` and `def`
21524    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21525    cx.assert_editor_state(indoc! {"
21526        def main():
21527                ˇtry:
21528                    ˇfetch()
21529                ˇexcept ValueError:
21530                    ˇhandle_error()
21531                ˇelse:
21532                    ˇmatch value:
21533                        ˇcase _:
21534                ˇfinally:
21535                    ˇdef status():
21536                        ˇreturn 0
21537    "});
21538}
21539
21540#[gpui::test]
21541async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21542    init_test(cx, |_| {});
21543
21544    let mut cx = EditorTestContext::new(cx).await;
21545    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21546    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21547
21548    // test `else` auto outdents when typed inside `if` block
21549    cx.set_state(indoc! {"
21550        def main():
21551            if i == 2:
21552                return
21553                ˇ
21554    "});
21555    cx.update_editor(|editor, window, cx| {
21556        editor.handle_input("else:", window, cx);
21557    });
21558    cx.assert_editor_state(indoc! {"
21559        def main():
21560            if i == 2:
21561                return
21562            else:ˇ
21563    "});
21564
21565    // test `except` auto outdents when typed inside `try` block
21566    cx.set_state(indoc! {"
21567        def main():
21568            try:
21569                i = 2
21570                ˇ
21571    "});
21572    cx.update_editor(|editor, window, cx| {
21573        editor.handle_input("except:", window, cx);
21574    });
21575    cx.assert_editor_state(indoc! {"
21576        def main():
21577            try:
21578                i = 2
21579            except:ˇ
21580    "});
21581
21582    // test `else` auto outdents when typed inside `except` block
21583    cx.set_state(indoc! {"
21584        def main():
21585            try:
21586                i = 2
21587            except:
21588                j = 2
21589                ˇ
21590    "});
21591    cx.update_editor(|editor, window, cx| {
21592        editor.handle_input("else:", window, cx);
21593    });
21594    cx.assert_editor_state(indoc! {"
21595        def main():
21596            try:
21597                i = 2
21598            except:
21599                j = 2
21600            else:ˇ
21601    "});
21602
21603    // test `finally` auto outdents when typed inside `else` block
21604    cx.set_state(indoc! {"
21605        def main():
21606            try:
21607                i = 2
21608            except:
21609                j = 2
21610            else:
21611                k = 2
21612                ˇ
21613    "});
21614    cx.update_editor(|editor, window, cx| {
21615        editor.handle_input("finally:", window, cx);
21616    });
21617    cx.assert_editor_state(indoc! {"
21618        def main():
21619            try:
21620                i = 2
21621            except:
21622                j = 2
21623            else:
21624                k = 2
21625            finally:ˇ
21626    "});
21627
21628    // TODO: test `except` auto outdents when typed inside `try` block right after for block
21629    // cx.set_state(indoc! {"
21630    //     def main():
21631    //         try:
21632    //             for i in range(n):
21633    //                 pass
21634    //             ˇ
21635    // "});
21636    // cx.update_editor(|editor, window, cx| {
21637    //     editor.handle_input("except:", window, cx);
21638    // });
21639    // cx.assert_editor_state(indoc! {"
21640    //     def main():
21641    //         try:
21642    //             for i in range(n):
21643    //                 pass
21644    //         except:ˇ
21645    // "});
21646
21647    // TODO: test `else` auto outdents when typed inside `except` block right after for block
21648    // cx.set_state(indoc! {"
21649    //     def main():
21650    //         try:
21651    //             i = 2
21652    //         except:
21653    //             for i in range(n):
21654    //                 pass
21655    //             ˇ
21656    // "});
21657    // cx.update_editor(|editor, window, cx| {
21658    //     editor.handle_input("else:", window, cx);
21659    // });
21660    // cx.assert_editor_state(indoc! {"
21661    //     def main():
21662    //         try:
21663    //             i = 2
21664    //         except:
21665    //             for i in range(n):
21666    //                 pass
21667    //         else:ˇ
21668    // "});
21669
21670    // TODO: test `finally` auto outdents when typed inside `else` block right after for block
21671    // cx.set_state(indoc! {"
21672    //     def main():
21673    //         try:
21674    //             i = 2
21675    //         except:
21676    //             j = 2
21677    //         else:
21678    //             for i in range(n):
21679    //                 pass
21680    //             ˇ
21681    // "});
21682    // cx.update_editor(|editor, window, cx| {
21683    //     editor.handle_input("finally:", window, cx);
21684    // });
21685    // cx.assert_editor_state(indoc! {"
21686    //     def main():
21687    //         try:
21688    //             i = 2
21689    //         except:
21690    //             j = 2
21691    //         else:
21692    //             for i in range(n):
21693    //                 pass
21694    //         finally:ˇ
21695    // "});
21696
21697    // test `else` stays at correct indent when typed after `for` block
21698    cx.set_state(indoc! {"
21699        def main():
21700            for i in range(10):
21701                if i == 3:
21702                    break
21703            ˇ
21704    "});
21705    cx.update_editor(|editor, window, cx| {
21706        editor.handle_input("else:", window, cx);
21707    });
21708    cx.assert_editor_state(indoc! {"
21709        def main():
21710            for i in range(10):
21711                if i == 3:
21712                    break
21713            else:ˇ
21714    "});
21715
21716    // test does not outdent on typing after line with square brackets
21717    cx.set_state(indoc! {"
21718        def f() -> list[str]:
21719            ˇ
21720    "});
21721    cx.update_editor(|editor, window, cx| {
21722        editor.handle_input("a", window, cx);
21723    });
21724    cx.assert_editor_state(indoc! {"
21725        def f() -> list[str]:
2172621727    "});
21728}
21729
21730#[gpui::test]
21731async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
21732    init_test(cx, |_| {});
21733    update_test_language_settings(cx, |settings| {
21734        settings.defaults.extend_comment_on_newline = Some(false);
21735    });
21736    let mut cx = EditorTestContext::new(cx).await;
21737    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21738    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21739
21740    // test correct indent after newline on comment
21741    cx.set_state(indoc! {"
21742        # COMMENT:ˇ
21743    "});
21744    cx.update_editor(|editor, window, cx| {
21745        editor.newline(&Newline, window, cx);
21746    });
21747    cx.assert_editor_state(indoc! {"
21748        # COMMENT:
21749        ˇ
21750    "});
21751
21752    // test correct indent after newline in brackets
21753    cx.set_state(indoc! {"
21754        {ˇ}
21755    "});
21756    cx.update_editor(|editor, window, cx| {
21757        editor.newline(&Newline, window, cx);
21758    });
21759    cx.run_until_parked();
21760    cx.assert_editor_state(indoc! {"
21761        {
21762            ˇ
21763        }
21764    "});
21765
21766    cx.set_state(indoc! {"
21767        (ˇ)
21768    "});
21769    cx.update_editor(|editor, window, cx| {
21770        editor.newline(&Newline, window, cx);
21771    });
21772    cx.run_until_parked();
21773    cx.assert_editor_state(indoc! {"
21774        (
21775            ˇ
21776        )
21777    "});
21778
21779    // do not indent after empty lists or dictionaries
21780    cx.set_state(indoc! {"
21781        a = []ˇ
21782    "});
21783    cx.update_editor(|editor, window, cx| {
21784        editor.newline(&Newline, window, cx);
21785    });
21786    cx.run_until_parked();
21787    cx.assert_editor_state(indoc! {"
21788        a = []
21789        ˇ
21790    "});
21791}
21792
21793fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
21794    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
21795    point..point
21796}
21797
21798#[track_caller]
21799fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
21800    let (text, ranges) = marked_text_ranges(marked_text, true);
21801    assert_eq!(editor.text(cx), text);
21802    assert_eq!(
21803        editor.selections.ranges(cx),
21804        ranges,
21805        "Assert selections are {}",
21806        marked_text
21807    );
21808}
21809
21810pub fn handle_signature_help_request(
21811    cx: &mut EditorLspTestContext,
21812    mocked_response: lsp::SignatureHelp,
21813) -> impl Future<Output = ()> + use<> {
21814    let mut request =
21815        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
21816            let mocked_response = mocked_response.clone();
21817            async move { Ok(Some(mocked_response)) }
21818        });
21819
21820    async move {
21821        request.next().await;
21822    }
21823}
21824
21825#[track_caller]
21826pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
21827    cx.update_editor(|editor, _, _| {
21828        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
21829            let entries = menu.entries.borrow();
21830            let entries = entries
21831                .iter()
21832                .map(|entry| entry.string.as_str())
21833                .collect::<Vec<_>>();
21834            assert_eq!(entries, expected);
21835        } else {
21836            panic!("Expected completions menu");
21837        }
21838    });
21839}
21840
21841/// Handle completion request passing a marked string specifying where the completion
21842/// should be triggered from using '|' character, what range should be replaced, and what completions
21843/// should be returned using '<' and '>' to delimit the range.
21844///
21845/// Also see `handle_completion_request_with_insert_and_replace`.
21846#[track_caller]
21847pub fn handle_completion_request(
21848    marked_string: &str,
21849    completions: Vec<&'static str>,
21850    is_incomplete: bool,
21851    counter: Arc<AtomicUsize>,
21852    cx: &mut EditorLspTestContext,
21853) -> impl Future<Output = ()> {
21854    let complete_from_marker: TextRangeMarker = '|'.into();
21855    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21856    let (_, mut marked_ranges) = marked_text_ranges_by(
21857        marked_string,
21858        vec![complete_from_marker.clone(), replace_range_marker.clone()],
21859    );
21860
21861    let complete_from_position =
21862        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21863    let replace_range =
21864        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21865
21866    let mut request =
21867        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21868            let completions = completions.clone();
21869            counter.fetch_add(1, atomic::Ordering::Release);
21870            async move {
21871                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21872                assert_eq!(
21873                    params.text_document_position.position,
21874                    complete_from_position
21875                );
21876                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21877                    is_incomplete: is_incomplete,
21878                    item_defaults: None,
21879                    items: completions
21880                        .iter()
21881                        .map(|completion_text| lsp::CompletionItem {
21882                            label: completion_text.to_string(),
21883                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21884                                range: replace_range,
21885                                new_text: completion_text.to_string(),
21886                            })),
21887                            ..Default::default()
21888                        })
21889                        .collect(),
21890                })))
21891            }
21892        });
21893
21894    async move {
21895        request.next().await;
21896    }
21897}
21898
21899/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
21900/// given instead, which also contains an `insert` range.
21901///
21902/// This function uses markers to define ranges:
21903/// - `|` marks the cursor position
21904/// - `<>` marks the replace range
21905/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
21906pub fn handle_completion_request_with_insert_and_replace(
21907    cx: &mut EditorLspTestContext,
21908    marked_string: &str,
21909    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
21910    counter: Arc<AtomicUsize>,
21911) -> impl Future<Output = ()> {
21912    let complete_from_marker: TextRangeMarker = '|'.into();
21913    let replace_range_marker: TextRangeMarker = ('<', '>').into();
21914    let insert_range_marker: TextRangeMarker = ('{', '}').into();
21915
21916    let (_, mut marked_ranges) = marked_text_ranges_by(
21917        marked_string,
21918        vec![
21919            complete_from_marker.clone(),
21920            replace_range_marker.clone(),
21921            insert_range_marker.clone(),
21922        ],
21923    );
21924
21925    let complete_from_position =
21926        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
21927    let replace_range =
21928        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
21929
21930    let insert_range = match marked_ranges.remove(&insert_range_marker) {
21931        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
21932        _ => lsp::Range {
21933            start: replace_range.start,
21934            end: complete_from_position,
21935        },
21936    };
21937
21938    let mut request =
21939        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
21940            let completions = completions.clone();
21941            counter.fetch_add(1, atomic::Ordering::Release);
21942            async move {
21943                assert_eq!(params.text_document_position.text_document.uri, url.clone());
21944                assert_eq!(
21945                    params.text_document_position.position, complete_from_position,
21946                    "marker `|` position doesn't match",
21947                );
21948                Ok(Some(lsp::CompletionResponse::Array(
21949                    completions
21950                        .iter()
21951                        .map(|(label, new_text)| lsp::CompletionItem {
21952                            label: label.to_string(),
21953                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21954                                lsp::InsertReplaceEdit {
21955                                    insert: insert_range,
21956                                    replace: replace_range,
21957                                    new_text: new_text.to_string(),
21958                                },
21959                            )),
21960                            ..Default::default()
21961                        })
21962                        .collect(),
21963                )))
21964            }
21965        });
21966
21967    async move {
21968        request.next().await;
21969    }
21970}
21971
21972fn handle_resolve_completion_request(
21973    cx: &mut EditorLspTestContext,
21974    edits: Option<Vec<(&'static str, &'static str)>>,
21975) -> impl Future<Output = ()> {
21976    let edits = edits.map(|edits| {
21977        edits
21978            .iter()
21979            .map(|(marked_string, new_text)| {
21980                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
21981                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
21982                lsp::TextEdit::new(replace_range, new_text.to_string())
21983            })
21984            .collect::<Vec<_>>()
21985    });
21986
21987    let mut request =
21988        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21989            let edits = edits.clone();
21990            async move {
21991                Ok(lsp::CompletionItem {
21992                    additional_text_edits: edits,
21993                    ..Default::default()
21994                })
21995            }
21996        });
21997
21998    async move {
21999        request.next().await;
22000    }
22001}
22002
22003pub(crate) fn update_test_language_settings(
22004    cx: &mut TestAppContext,
22005    f: impl Fn(&mut AllLanguageSettingsContent),
22006) {
22007    cx.update(|cx| {
22008        SettingsStore::update_global(cx, |store, cx| {
22009            store.update_user_settings::<AllLanguageSettings>(cx, f);
22010        });
22011    });
22012}
22013
22014pub(crate) fn update_test_project_settings(
22015    cx: &mut TestAppContext,
22016    f: impl Fn(&mut ProjectSettings),
22017) {
22018    cx.update(|cx| {
22019        SettingsStore::update_global(cx, |store, cx| {
22020            store.update_user_settings::<ProjectSettings>(cx, f);
22021        });
22022    });
22023}
22024
22025pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22026    cx.update(|cx| {
22027        assets::Assets.load_test_fonts(cx);
22028        let store = SettingsStore::test(cx);
22029        cx.set_global(store);
22030        theme::init(theme::LoadThemes::JustBase, cx);
22031        release_channel::init(SemanticVersion::default(), cx);
22032        client::init_settings(cx);
22033        language::init(cx);
22034        Project::init_settings(cx);
22035        workspace::init_settings(cx);
22036        crate::init(cx);
22037    });
22038
22039    update_test_language_settings(cx, f);
22040}
22041
22042#[track_caller]
22043fn assert_hunk_revert(
22044    not_reverted_text_with_selections: &str,
22045    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22046    expected_reverted_text_with_selections: &str,
22047    base_text: &str,
22048    cx: &mut EditorLspTestContext,
22049) {
22050    cx.set_state(not_reverted_text_with_selections);
22051    cx.set_head_text(base_text);
22052    cx.executor().run_until_parked();
22053
22054    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22055        let snapshot = editor.snapshot(window, cx);
22056        let reverted_hunk_statuses = snapshot
22057            .buffer_snapshot
22058            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22059            .map(|hunk| hunk.status().kind)
22060            .collect::<Vec<_>>();
22061
22062        editor.git_restore(&Default::default(), window, cx);
22063        reverted_hunk_statuses
22064    });
22065    cx.executor().run_until_parked();
22066    cx.assert_editor_state(expected_reverted_text_with_selections);
22067    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22068}
22069
22070#[gpui::test(iterations = 10)]
22071async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22072    init_test(cx, |_| {});
22073
22074    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22075    let counter = diagnostic_requests.clone();
22076
22077    let fs = FakeFs::new(cx.executor());
22078    fs.insert_tree(
22079        path!("/a"),
22080        json!({
22081            "first.rs": "fn main() { let a = 5; }",
22082            "second.rs": "// Test file",
22083        }),
22084    )
22085    .await;
22086
22087    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22088    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22089    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22090
22091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22092    language_registry.add(rust_lang());
22093    let mut fake_servers = language_registry.register_fake_lsp(
22094        "Rust",
22095        FakeLspAdapter {
22096            capabilities: lsp::ServerCapabilities {
22097                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22098                    lsp::DiagnosticOptions {
22099                        identifier: None,
22100                        inter_file_dependencies: true,
22101                        workspace_diagnostics: true,
22102                        work_done_progress_options: Default::default(),
22103                    },
22104                )),
22105                ..Default::default()
22106            },
22107            ..Default::default()
22108        },
22109    );
22110
22111    let editor = workspace
22112        .update(cx, |workspace, window, cx| {
22113            workspace.open_abs_path(
22114                PathBuf::from(path!("/a/first.rs")),
22115                OpenOptions::default(),
22116                window,
22117                cx,
22118            )
22119        })
22120        .unwrap()
22121        .await
22122        .unwrap()
22123        .downcast::<Editor>()
22124        .unwrap();
22125    let fake_server = fake_servers.next().await.unwrap();
22126    let server_id = fake_server.server.server_id();
22127    let mut first_request = fake_server
22128        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22129            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22130            let result_id = Some(new_result_id.to_string());
22131            assert_eq!(
22132                params.text_document.uri,
22133                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22134            );
22135            async move {
22136                Ok(lsp::DocumentDiagnosticReportResult::Report(
22137                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22138                        related_documents: None,
22139                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22140                            items: Vec::new(),
22141                            result_id,
22142                        },
22143                    }),
22144                ))
22145            }
22146        });
22147
22148    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22149        project.update(cx, |project, cx| {
22150            let buffer_id = editor
22151                .read(cx)
22152                .buffer()
22153                .read(cx)
22154                .as_singleton()
22155                .expect("created a singleton buffer")
22156                .read(cx)
22157                .remote_id();
22158            let buffer_result_id = project
22159                .lsp_store()
22160                .read(cx)
22161                .result_id(server_id, buffer_id, cx);
22162            assert_eq!(expected, buffer_result_id);
22163        });
22164    };
22165
22166    ensure_result_id(None, cx);
22167    cx.executor().advance_clock(Duration::from_millis(60));
22168    cx.executor().run_until_parked();
22169    assert_eq!(
22170        diagnostic_requests.load(atomic::Ordering::Acquire),
22171        1,
22172        "Opening file should trigger diagnostic request"
22173    );
22174    first_request
22175        .next()
22176        .await
22177        .expect("should have sent the first diagnostics pull request");
22178    ensure_result_id(Some("1".to_string()), cx);
22179
22180    // Editing should trigger diagnostics
22181    editor.update_in(cx, |editor, window, cx| {
22182        editor.handle_input("2", window, cx)
22183    });
22184    cx.executor().advance_clock(Duration::from_millis(60));
22185    cx.executor().run_until_parked();
22186    assert_eq!(
22187        diagnostic_requests.load(atomic::Ordering::Acquire),
22188        2,
22189        "Editing should trigger diagnostic request"
22190    );
22191    ensure_result_id(Some("2".to_string()), cx);
22192
22193    // Moving cursor should not trigger diagnostic request
22194    editor.update_in(cx, |editor, window, cx| {
22195        editor.change_selections(None, window, cx, |s| {
22196            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22197        });
22198    });
22199    cx.executor().advance_clock(Duration::from_millis(60));
22200    cx.executor().run_until_parked();
22201    assert_eq!(
22202        diagnostic_requests.load(atomic::Ordering::Acquire),
22203        2,
22204        "Cursor movement should not trigger diagnostic request"
22205    );
22206    ensure_result_id(Some("2".to_string()), cx);
22207    // Multiple rapid edits should be debounced
22208    for _ in 0..5 {
22209        editor.update_in(cx, |editor, window, cx| {
22210            editor.handle_input("x", window, cx)
22211        });
22212    }
22213    cx.executor().advance_clock(Duration::from_millis(60));
22214    cx.executor().run_until_parked();
22215
22216    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22217    assert!(
22218        final_requests <= 4,
22219        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22220    );
22221    ensure_result_id(Some(final_requests.to_string()), cx);
22222}
22223
22224#[gpui::test]
22225async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22226    // Regression test for issue #11671
22227    // Previously, adding a cursor after moving multiple cursors would reset
22228    // the cursor count instead of adding to the existing cursors.
22229    init_test(cx, |_| {});
22230    let mut cx = EditorTestContext::new(cx).await;
22231
22232    // Create a simple buffer with cursor at start
22233    cx.set_state(indoc! {"
22234        ˇaaaa
22235        bbbb
22236        cccc
22237        dddd
22238        eeee
22239        ffff
22240        gggg
22241        hhhh"});
22242
22243    // Add 2 cursors below (so we have 3 total)
22244    cx.update_editor(|editor, window, cx| {
22245        editor.add_selection_below(&Default::default(), window, cx);
22246        editor.add_selection_below(&Default::default(), window, cx);
22247    });
22248
22249    // Verify we have 3 cursors
22250    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22251    assert_eq!(
22252        initial_count, 3,
22253        "Should have 3 cursors after adding 2 below"
22254    );
22255
22256    // Move down one line
22257    cx.update_editor(|editor, window, cx| {
22258        editor.move_down(&MoveDown, window, cx);
22259    });
22260
22261    // Add another cursor below
22262    cx.update_editor(|editor, window, cx| {
22263        editor.add_selection_below(&Default::default(), window, cx);
22264    });
22265
22266    // Should now have 4 cursors (3 original + 1 new)
22267    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22268    assert_eq!(
22269        final_count, 4,
22270        "Should have 4 cursors after moving and adding another"
22271    );
22272}
22273
22274#[gpui::test(iterations = 10)]
22275async fn test_document_colors(cx: &mut TestAppContext) {
22276    let expected_color = Rgba {
22277        r: 0.33,
22278        g: 0.33,
22279        b: 0.33,
22280        a: 0.33,
22281    };
22282
22283    init_test(cx, |_| {});
22284
22285    let fs = FakeFs::new(cx.executor());
22286    fs.insert_tree(
22287        path!("/a"),
22288        json!({
22289            "first.rs": "fn main() { let a = 5; }",
22290        }),
22291    )
22292    .await;
22293
22294    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22295    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22296    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22297
22298    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22299    language_registry.add(rust_lang());
22300    let mut fake_servers = language_registry.register_fake_lsp(
22301        "Rust",
22302        FakeLspAdapter {
22303            capabilities: lsp::ServerCapabilities {
22304                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22305                ..lsp::ServerCapabilities::default()
22306            },
22307            name: "rust-analyzer",
22308            ..FakeLspAdapter::default()
22309        },
22310    );
22311    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22312        "Rust",
22313        FakeLspAdapter {
22314            capabilities: lsp::ServerCapabilities {
22315                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22316                ..lsp::ServerCapabilities::default()
22317            },
22318            name: "not-rust-analyzer",
22319            ..FakeLspAdapter::default()
22320        },
22321    );
22322
22323    let editor = workspace
22324        .update(cx, |workspace, window, cx| {
22325            workspace.open_abs_path(
22326                PathBuf::from(path!("/a/first.rs")),
22327                OpenOptions::default(),
22328                window,
22329                cx,
22330            )
22331        })
22332        .unwrap()
22333        .await
22334        .unwrap()
22335        .downcast::<Editor>()
22336        .unwrap();
22337    let fake_language_server = fake_servers.next().await.unwrap();
22338    let fake_language_server_without_capabilities =
22339        fake_servers_without_capabilities.next().await.unwrap();
22340    let requests_made = Arc::new(AtomicUsize::new(0));
22341    let closure_requests_made = Arc::clone(&requests_made);
22342    let mut color_request_handle = fake_language_server
22343        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22344            let requests_made = Arc::clone(&closure_requests_made);
22345            async move {
22346                assert_eq!(
22347                    params.text_document.uri,
22348                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22349                );
22350                requests_made.fetch_add(1, atomic::Ordering::Release);
22351                Ok(vec![
22352                    lsp::ColorInformation {
22353                        range: lsp::Range {
22354                            start: lsp::Position {
22355                                line: 0,
22356                                character: 0,
22357                            },
22358                            end: lsp::Position {
22359                                line: 0,
22360                                character: 1,
22361                            },
22362                        },
22363                        color: lsp::Color {
22364                            red: 0.33,
22365                            green: 0.33,
22366                            blue: 0.33,
22367                            alpha: 0.33,
22368                        },
22369                    },
22370                    lsp::ColorInformation {
22371                        range: lsp::Range {
22372                            start: lsp::Position {
22373                                line: 0,
22374                                character: 0,
22375                            },
22376                            end: lsp::Position {
22377                                line: 0,
22378                                character: 1,
22379                            },
22380                        },
22381                        color: lsp::Color {
22382                            red: 0.33,
22383                            green: 0.33,
22384                            blue: 0.33,
22385                            alpha: 0.33,
22386                        },
22387                    },
22388                ])
22389            }
22390        });
22391
22392    let _handle = fake_language_server_without_capabilities
22393        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22394            panic!("Should not be called");
22395        });
22396    cx.executor().advance_clock(Duration::from_millis(100));
22397    color_request_handle.next().await.unwrap();
22398    cx.run_until_parked();
22399    assert_eq!(
22400        1,
22401        requests_made.load(atomic::Ordering::Acquire),
22402        "Should query for colors once per editor open"
22403    );
22404    editor.update_in(cx, |editor, _, cx| {
22405        assert_eq!(
22406            vec![expected_color],
22407            extract_color_inlays(editor, cx),
22408            "Should have an initial inlay"
22409        );
22410    });
22411
22412    // opening another file in a split should not influence the LSP query counter
22413    workspace
22414        .update(cx, |workspace, window, cx| {
22415            assert_eq!(
22416                workspace.panes().len(),
22417                1,
22418                "Should have one pane with one editor"
22419            );
22420            workspace.activate_pane_at_index(&ActivatePane(1), window, cx);
22421        })
22422        .unwrap();
22423    cx.run_until_parked();
22424    workspace
22425        .update(cx, |workspace, _, cx| {
22426            let panes = workspace.panes();
22427            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
22428            for pane in panes {
22429                let editor = pane
22430                    .read(cx)
22431                    .active_item()
22432                    .and_then(|item| item.downcast::<Editor>())
22433                    .expect("Should have opened an editor in each split");
22434                let editor_file = editor
22435                    .read(cx)
22436                    .buffer()
22437                    .read(cx)
22438                    .as_singleton()
22439                    .expect("test deals with singleton buffers")
22440                    .read(cx)
22441                    .file()
22442                    .expect("test buffese should have a file")
22443                    .path();
22444                assert_eq!(
22445                    editor_file.as_ref(),
22446                    Path::new("first.rs"),
22447                    "Both editors should be opened for the same file"
22448                )
22449            }
22450        })
22451        .unwrap();
22452
22453    cx.executor().advance_clock(Duration::from_millis(500));
22454    let save = editor.update_in(cx, |editor, window, cx| {
22455        editor.move_to_end(&MoveToEnd, window, cx);
22456        editor.handle_input("dirty", window, cx);
22457        editor.save(
22458            SaveOptions {
22459                format: true,
22460                autosave: true,
22461            },
22462            project.clone(),
22463            window,
22464            cx,
22465        )
22466    });
22467    save.await.unwrap();
22468
22469    color_request_handle.next().await.unwrap();
22470    cx.run_until_parked();
22471    assert_eq!(
22472        3,
22473        requests_made.load(atomic::Ordering::Acquire),
22474        "Should query for colors once per save and once per formatting after save"
22475    );
22476
22477    drop(editor);
22478    let close = workspace
22479        .update(cx, |workspace, window, cx| {
22480            workspace.active_pane().update(cx, |pane, cx| {
22481                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22482            })
22483        })
22484        .unwrap();
22485    close.await.unwrap();
22486    let close = workspace
22487        .update(cx, |workspace, window, cx| {
22488            workspace.active_pane().update(cx, |pane, cx| {
22489                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22490            })
22491        })
22492        .unwrap();
22493    close.await.unwrap();
22494    assert_eq!(
22495        3,
22496        requests_made.load(atomic::Ordering::Acquire),
22497        "After saving and closing all editors, no extra requests should be made"
22498    );
22499    workspace
22500        .update(cx, |workspace, _, cx| {
22501            assert!(
22502                workspace.active_item(cx).is_none(),
22503                "Should close all editors"
22504            )
22505        })
22506        .unwrap();
22507
22508    workspace
22509        .update(cx, |workspace, window, cx| {
22510            workspace.active_pane().update(cx, |pane, cx| {
22511                pane.navigate_backward(window, cx);
22512            })
22513        })
22514        .unwrap();
22515    cx.executor().advance_clock(Duration::from_millis(100));
22516    cx.run_until_parked();
22517    let editor = workspace
22518        .update(cx, |workspace, _, cx| {
22519            workspace
22520                .active_item(cx)
22521                .expect("Should have reopened the editor again after navigating back")
22522                .downcast::<Editor>()
22523                .expect("Should be an editor")
22524        })
22525        .unwrap();
22526    color_request_handle.next().await.unwrap();
22527    assert_eq!(
22528        3,
22529        requests_made.load(atomic::Ordering::Acquire),
22530        "Cache should be reused on buffer close and reopen"
22531    );
22532    editor.update(cx, |editor, cx| {
22533        assert_eq!(
22534            vec![expected_color],
22535            extract_color_inlays(editor, cx),
22536            "Should have an initial inlay"
22537        );
22538    });
22539}
22540
22541#[track_caller]
22542fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22543    editor
22544        .all_inlays(cx)
22545        .into_iter()
22546        .filter_map(|inlay| inlay.get_color())
22547        .map(Rgba::from)
22548        .collect()
22549}