editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::{CompletionParams, DEFAULT_LSP_REQUEST_TIMEOUT};
   38use multi_buffer::{
   39    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, DelayMs, EditorSettingsContent, GlobalLspSettingsContent,
   52    IndentGuideBackgroundColoring, IndentGuideColoring, InlayHintSettingsContent,
   53    ProjectSettingsContent, SearchSettingsContent, SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, NavigationEntry, OpenOptions, ViewId,
   71    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   72    register_project_item,
   73};
   74
   75fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   76    editor
   77        .selections
   78        .display_ranges(&editor.display_snapshot(cx))
   79}
   80
   81#[gpui::test]
   82fn test_edit_events(cx: &mut TestAppContext) {
   83    init_test(cx, |_| {});
   84
   85    let buffer = cx.new(|cx| {
   86        let mut buffer = language::Buffer::local("123456", cx);
   87        buffer.set_group_interval(Duration::from_secs(1));
   88        buffer
   89    });
   90
   91    let events = Rc::new(RefCell::new(Vec::new()));
   92    let editor1 = cx.add_window({
   93        let events = events.clone();
   94        |window, cx| {
   95            let entity = cx.entity();
   96            cx.subscribe_in(
   97                &entity,
   98                window,
   99                move |_, _, event: &EditorEvent, _, _| match event {
  100                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  101                    EditorEvent::BufferEdited => {
  102                        events.borrow_mut().push(("editor1", "buffer edited"))
  103                    }
  104                    _ => {}
  105                },
  106            )
  107            .detach();
  108            Editor::for_buffer(buffer.clone(), None, window, cx)
  109        }
  110    });
  111
  112    let editor2 = cx.add_window({
  113        let events = events.clone();
  114        |window, cx| {
  115            cx.subscribe_in(
  116                &cx.entity(),
  117                window,
  118                move |_, _, event: &EditorEvent, _, _| match event {
  119                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  120                    EditorEvent::BufferEdited => {
  121                        events.borrow_mut().push(("editor2", "buffer edited"))
  122                    }
  123                    _ => {}
  124                },
  125            )
  126            .detach();
  127            Editor::for_buffer(buffer.clone(), None, window, cx)
  128        }
  129    });
  130
  131    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  132
  133    // Mutating editor 1 will emit an `Edited` event only for that editor.
  134    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  135    assert_eq!(
  136        mem::take(&mut *events.borrow_mut()),
  137        [
  138            ("editor1", "edited"),
  139            ("editor1", "buffer edited"),
  140            ("editor2", "buffer edited"),
  141        ]
  142    );
  143
  144    // Mutating editor 2 will emit an `Edited` event only for that editor.
  145    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  146    assert_eq!(
  147        mem::take(&mut *events.borrow_mut()),
  148        [
  149            ("editor2", "edited"),
  150            ("editor1", "buffer edited"),
  151            ("editor2", "buffer edited"),
  152        ]
  153    );
  154
  155    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  156    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  157    assert_eq!(
  158        mem::take(&mut *events.borrow_mut()),
  159        [
  160            ("editor1", "edited"),
  161            ("editor1", "buffer edited"),
  162            ("editor2", "buffer edited"),
  163        ]
  164    );
  165
  166    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  167    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  168    assert_eq!(
  169        mem::take(&mut *events.borrow_mut()),
  170        [
  171            ("editor1", "edited"),
  172            ("editor1", "buffer edited"),
  173            ("editor2", "buffer edited"),
  174        ]
  175    );
  176
  177    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  178    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  179    assert_eq!(
  180        mem::take(&mut *events.borrow_mut()),
  181        [
  182            ("editor2", "edited"),
  183            ("editor1", "buffer edited"),
  184            ("editor2", "buffer edited"),
  185        ]
  186    );
  187
  188    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  189    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  190    assert_eq!(
  191        mem::take(&mut *events.borrow_mut()),
  192        [
  193            ("editor2", "edited"),
  194            ("editor1", "buffer edited"),
  195            ("editor2", "buffer edited"),
  196        ]
  197    );
  198
  199    // No event is emitted when the mutation is a no-op.
  200    _ = editor2.update(cx, |editor, window, cx| {
  201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  202            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  203        });
  204
  205        editor.backspace(&Backspace, window, cx);
  206    });
  207    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  208}
  209
  210#[gpui::test]
  211fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  212    init_test(cx, |_| {});
  213
  214    let mut now = Instant::now();
  215    let group_interval = Duration::from_millis(1);
  216    let buffer = cx.new(|cx| {
  217        let mut buf = language::Buffer::local("123456", cx);
  218        buf.set_group_interval(group_interval);
  219        buf
  220    });
  221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  222    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  223
  224    _ = editor.update(cx, |editor, window, cx| {
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  228        });
  229
  230        editor.insert("cd", window, cx);
  231        editor.end_transaction_at(now, cx);
  232        assert_eq!(editor.text(cx), "12cd56");
  233        assert_eq!(
  234            editor.selections.ranges(&editor.display_snapshot(cx)),
  235            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  236        );
  237
  238        editor.start_transaction_at(now, window, cx);
  239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  240            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  241        });
  242        editor.insert("e", window, cx);
  243        editor.end_transaction_at(now, cx);
  244        assert_eq!(editor.text(cx), "12cde6");
  245        assert_eq!(
  246            editor.selections.ranges(&editor.display_snapshot(cx)),
  247            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  248        );
  249
  250        now += group_interval + Duration::from_millis(1);
  251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  252            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  253        });
  254
  255        // Simulate an edit in another editor
  256        buffer.update(cx, |buffer, cx| {
  257            buffer.start_transaction_at(now, cx);
  258            buffer.edit(
  259                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  260                None,
  261                cx,
  262            );
  263            buffer.edit(
  264                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  265                None,
  266                cx,
  267            );
  268            buffer.end_transaction_at(now, cx);
  269        });
  270
  271        assert_eq!(editor.text(cx), "ab2cde6");
  272        assert_eq!(
  273            editor.selections.ranges(&editor.display_snapshot(cx)),
  274            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  275        );
  276
  277        // Last transaction happened past the group interval in a different editor.
  278        // Undo it individually and don't restore selections.
  279        editor.undo(&Undo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  284        );
  285
  286        // First two transactions happened within the group interval in this editor.
  287        // Undo them together and restore selections.
  288        editor.undo(&Undo, window, cx);
  289        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  290        assert_eq!(editor.text(cx), "123456");
  291        assert_eq!(
  292            editor.selections.ranges(&editor.display_snapshot(cx)),
  293            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  294        );
  295
  296        // Redo the first two transactions together.
  297        editor.redo(&Redo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299        assert_eq!(
  300            editor.selections.ranges(&editor.display_snapshot(cx)),
  301            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  302        );
  303
  304        // Redo the last transaction on its own.
  305        editor.redo(&Redo, window, cx);
  306        assert_eq!(editor.text(cx), "ab2cde6");
  307        assert_eq!(
  308            editor.selections.ranges(&editor.display_snapshot(cx)),
  309            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  310        );
  311
  312        // Test empty transactions.
  313        editor.start_transaction_at(now, window, cx);
  314        editor.end_transaction_at(now, cx);
  315        editor.undo(&Undo, window, cx);
  316        assert_eq!(editor.text(cx), "12cde6");
  317    });
  318}
  319
  320#[gpui::test]
  321fn test_ime_composition(cx: &mut TestAppContext) {
  322    init_test(cx, |_| {});
  323
  324    let buffer = cx.new(|cx| {
  325        let mut buffer = language::Buffer::local("abcde", cx);
  326        // Ensure automatic grouping doesn't occur.
  327        buffer.set_group_interval(Duration::ZERO);
  328        buffer
  329    });
  330
  331    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  332    cx.add_window(|window, cx| {
  333        let mut editor = build_editor(buffer.clone(), window, cx);
  334
  335        // Start a new IME composition.
  336        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  337        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  339        assert_eq!(editor.text(cx), "äbcde");
  340        assert_eq!(
  341            editor.marked_text_ranges(cx),
  342            Some(vec![
  343                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  344            ])
  345        );
  346
  347        // Finalize IME composition.
  348        editor.replace_text_in_range(None, "ā", window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // IME composition edits are grouped and are undone/redone at once.
  353        editor.undo(&Default::default(), window, cx);
  354        assert_eq!(editor.text(cx), "abcde");
  355        assert_eq!(editor.marked_text_ranges(cx), None);
  356        editor.redo(&Default::default(), window, cx);
  357        assert_eq!(editor.text(cx), "ābcde");
  358        assert_eq!(editor.marked_text_ranges(cx), None);
  359
  360        // Start a new IME composition.
  361        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  362        assert_eq!(
  363            editor.marked_text_ranges(cx),
  364            Some(vec![
  365                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  366            ])
  367        );
  368
  369        // Undoing during an IME composition cancels it.
  370        editor.undo(&Default::default(), window, cx);
  371        assert_eq!(editor.text(cx), "ābcde");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  375        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  376        assert_eq!(editor.text(cx), "ābcdè");
  377        assert_eq!(
  378            editor.marked_text_ranges(cx),
  379            Some(vec![
  380                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  381            ])
  382        );
  383
  384        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  385        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  386        assert_eq!(editor.text(cx), "ābcdę");
  387        assert_eq!(editor.marked_text_ranges(cx), None);
  388
  389        // Start a new IME composition with multiple cursors.
  390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  391            s.select_ranges([
  392                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  393                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  395            ])
  396        });
  397        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  398        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  399        assert_eq!(
  400            editor.marked_text_ranges(cx),
  401            Some(vec![
  402                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  403                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  405            ])
  406        );
  407
  408        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  409        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  410        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  411        assert_eq!(
  412            editor.marked_text_ranges(cx),
  413            Some(vec![
  414                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  415                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  417            ])
  418        );
  419
  420        // Finalize IME composition with multiple cursors.
  421        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  422        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  423        assert_eq!(editor.marked_text_ranges(cx), None);
  424
  425        editor
  426    });
  427}
  428
  429#[gpui::test]
  430fn test_selection_with_mouse(cx: &mut TestAppContext) {
  431    init_test(cx, |_| {});
  432
  433    let editor = cx.add_window(|window, cx| {
  434        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  435        build_editor(buffer, window, cx)
  436    });
  437
  438    _ = editor.update(cx, |editor, window, cx| {
  439        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  440    });
  441    assert_eq!(
  442        editor
  443            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  444            .unwrap(),
  445        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  446    );
  447
  448    _ = editor.update(cx, |editor, window, cx| {
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(3), 3),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  461            .unwrap(),
  462        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  463    );
  464
  465    _ = editor.update(cx, |editor, window, cx| {
  466        editor.update_selection(
  467            DisplayPoint::new(DisplayRow(1), 1),
  468            0,
  469            gpui::Point::<f32>::default(),
  470            window,
  471            cx,
  472        );
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  480    );
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.end_selection(window, cx);
  484        editor.update_selection(
  485            DisplayPoint::new(DisplayRow(3), 3),
  486            0,
  487            gpui::Point::<f32>::default(),
  488            window,
  489            cx,
  490        );
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  496            .unwrap(),
  497        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  498    );
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  502        editor.update_selection(
  503            DisplayPoint::new(DisplayRow(0), 0),
  504            0,
  505            gpui::Point::<f32>::default(),
  506            window,
  507            cx,
  508        );
  509    });
  510
  511    assert_eq!(
  512        editor
  513            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  514            .unwrap(),
  515        [
  516            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  517            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  518        ]
  519    );
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  544    });
  545
  546    _ = editor.update(cx, |editor, window, cx| {
  547        editor.end_selection(window, cx);
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.end_selection(window, cx);
  556    });
  557
  558    assert_eq!(
  559        editor
  560            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  561            .unwrap(),
  562        [
  563            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  564            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  565        ]
  566    );
  567
  568    _ = editor.update(cx, |editor, window, cx| {
  569        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  570    });
  571
  572    _ = editor.update(cx, |editor, window, cx| {
  573        editor.end_selection(window, cx);
  574    });
  575
  576    assert_eq!(
  577        editor
  578            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  579            .unwrap(),
  580        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  581    );
  582}
  583
  584#[gpui::test]
  585fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  586    init_test(cx, |_| {});
  587
  588    let editor = cx.add_window(|window, cx| {
  589        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  590        build_editor(buffer, window, cx)
  591    });
  592
  593    _ = editor.update(cx, |editor, window, cx| {
  594        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  595        assert_eq!(
  596            display_ranges(editor, cx),
  597            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  598        );
  599    });
  600
  601    _ = editor.update(cx, |editor, window, cx| {
  602        editor.update_selection(
  603            DisplayPoint::new(DisplayRow(3), 3),
  604            0,
  605            gpui::Point::<f32>::default(),
  606            window,
  607            cx,
  608        );
  609        assert_eq!(
  610            display_ranges(editor, cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  612        );
  613    });
  614
  615    _ = editor.update(cx, |editor, window, cx| {
  616        editor.cancel(&Cancel, window, cx);
  617        editor.update_selection(
  618            DisplayPoint::new(DisplayRow(1), 1),
  619            0,
  620            gpui::Point::<f32>::default(),
  621            window,
  622            cx,
  623        );
  624        assert_eq!(
  625            display_ranges(editor, cx),
  626            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  627        );
  628    });
  629}
  630
  631#[gpui::test]
  632fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  633    init_test(cx, |_| {});
  634
  635    let editor = cx.add_window(|window, cx| {
  636        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  637        build_editor(buffer, window, cx)
  638    });
  639
  640    _ = editor.update(cx, |editor, window, cx| {
  641        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  642        assert_eq!(
  643            display_ranges(editor, cx),
  644            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  645        );
  646
  647        editor.move_down(&Default::default(), window, cx);
  648        assert_eq!(
  649            display_ranges(editor, cx),
  650            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  651        );
  652
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  654        assert_eq!(
  655            display_ranges(editor, cx),
  656            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  657        );
  658
  659        editor.move_up(&Default::default(), window, cx);
  660        assert_eq!(
  661            display_ranges(editor, cx),
  662            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  663        );
  664    });
  665}
  666
  667#[gpui::test]
  668fn test_extending_selection(cx: &mut TestAppContext) {
  669    init_test(cx, |_| {});
  670
  671    let editor = cx.add_window(|window, cx| {
  672        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  673        build_editor(buffer, window, cx)
  674    });
  675
  676    _ = editor.update(cx, |editor, window, cx| {
  677        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  678        editor.end_selection(window, cx);
  679        assert_eq!(
  680            display_ranges(editor, cx),
  681            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  682        );
  683
  684        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  685        editor.end_selection(window, cx);
  686        assert_eq!(
  687            display_ranges(editor, cx),
  688            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  689        );
  690
  691        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  692        editor.end_selection(window, cx);
  693        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  694        assert_eq!(
  695            display_ranges(editor, cx),
  696            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  697        );
  698
  699        editor.update_selection(
  700            DisplayPoint::new(DisplayRow(0), 1),
  701            0,
  702            gpui::Point::<f32>::default(),
  703            window,
  704            cx,
  705        );
  706        editor.end_selection(window, cx);
  707        assert_eq!(
  708            display_ranges(editor, cx),
  709            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  710        );
  711
  712        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  713        editor.end_selection(window, cx);
  714        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  715        editor.end_selection(window, cx);
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  719        );
  720
  721        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  722        assert_eq!(
  723            display_ranges(editor, cx),
  724            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  725        );
  726
  727        editor.update_selection(
  728            DisplayPoint::new(DisplayRow(0), 6),
  729            0,
  730            gpui::Point::<f32>::default(),
  731            window,
  732            cx,
  733        );
  734        assert_eq!(
  735            display_ranges(editor, cx),
  736            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  737        );
  738
  739        editor.update_selection(
  740            DisplayPoint::new(DisplayRow(0), 1),
  741            0,
  742            gpui::Point::<f32>::default(),
  743            window,
  744            cx,
  745        );
  746        editor.end_selection(window, cx);
  747        assert_eq!(
  748            display_ranges(editor, cx),
  749            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  750        );
  751    });
  752}
  753
  754#[gpui::test]
  755fn test_clone(cx: &mut TestAppContext) {
  756    init_test(cx, |_| {});
  757
  758    let (text, selection_ranges) = marked_text_ranges(
  759        indoc! {"
  760            one
  761            two
  762            threeˇ
  763            four
  764            fiveˇ
  765        "},
  766        true,
  767    );
  768
  769    let editor = cx.add_window(|window, cx| {
  770        let buffer = MultiBuffer::build_simple(&text, cx);
  771        build_editor(buffer, window, cx)
  772    });
  773
  774    _ = editor.update(cx, |editor, window, cx| {
  775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  776            s.select_ranges(
  777                selection_ranges
  778                    .iter()
  779                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  780            )
  781        });
  782        editor.fold_creases(
  783            vec![
  784                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  785                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  786            ],
  787            true,
  788            window,
  789            cx,
  790        );
  791    });
  792
  793    let cloned_editor = editor
  794        .update(cx, |editor, _, cx| {
  795            cx.open_window(Default::default(), |window, cx| {
  796                cx.new(|cx| editor.clone(window, cx))
  797            })
  798        })
  799        .unwrap()
  800        .unwrap();
  801
  802    let snapshot = editor
  803        .update(cx, |e, window, cx| e.snapshot(window, cx))
  804        .unwrap();
  805    let cloned_snapshot = cloned_editor
  806        .update(cx, |e, window, cx| e.snapshot(window, cx))
  807        .unwrap();
  808
  809    assert_eq!(
  810        cloned_editor
  811            .update(cx, |e, _, cx| e.display_text(cx))
  812            .unwrap(),
  813        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  814    );
  815    assert_eq!(
  816        cloned_snapshot
  817            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  818            .collect::<Vec<_>>(),
  819        snapshot
  820            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  821            .collect::<Vec<_>>(),
  822    );
  823    assert_set_eq!(
  824        cloned_editor
  825            .update(cx, |editor, _, cx| editor
  826                .selections
  827                .ranges::<Point>(&editor.display_snapshot(cx)))
  828            .unwrap(),
  829        editor
  830            .update(cx, |editor, _, cx| editor
  831                .selections
  832                .ranges(&editor.display_snapshot(cx)))
  833            .unwrap()
  834    );
  835    assert_set_eq!(
  836        cloned_editor
  837            .update(cx, |e, _window, cx| e
  838                .selections
  839                .display_ranges(&e.display_snapshot(cx)))
  840            .unwrap(),
  841        editor
  842            .update(cx, |e, _, cx| e
  843                .selections
  844                .display_ranges(&e.display_snapshot(cx)))
  845            .unwrap()
  846    );
  847}
  848
  849#[gpui::test]
  850async fn test_navigation_history(cx: &mut TestAppContext) {
  851    init_test(cx, |_| {});
  852
  853    use workspace::item::Item;
  854
  855    let fs = FakeFs::new(cx.executor());
  856    let project = Project::test(fs, [], cx).await;
  857    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  858    let pane = workspace
  859        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  860        .unwrap();
  861
  862    _ = workspace.update(cx, |_v, window, cx| {
  863        cx.new(|cx| {
  864            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  865            let mut editor = build_editor(buffer, window, cx);
  866            let handle = cx.entity();
  867            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  868
  869            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  870                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  871            }
  872
  873            // Move the cursor a small distance.
  874            // Nothing is added to the navigation history.
  875            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  876                s.select_display_ranges([
  877                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  878                ])
  879            });
  880            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  881                s.select_display_ranges([
  882                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  883                ])
  884            });
  885            assert!(pop_history(&mut editor, cx).is_none());
  886
  887            // Move the cursor a large distance.
  888            // The history can jump back to the previous position.
  889            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  890                s.select_display_ranges([
  891                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  892                ])
  893            });
  894            let nav_entry = pop_history(&mut editor, cx).unwrap();
  895            editor.navigate(nav_entry.data.unwrap(), window, cx);
  896            assert_eq!(nav_entry.item.id(), cx.entity_id());
  897            assert_eq!(
  898                editor
  899                    .selections
  900                    .display_ranges(&editor.display_snapshot(cx)),
  901                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  902            );
  903            assert!(pop_history(&mut editor, cx).is_none());
  904
  905            // Move the cursor a small distance via the mouse.
  906            // Nothing is added to the navigation history.
  907            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  908            editor.end_selection(window, cx);
  909            assert_eq!(
  910                editor
  911                    .selections
  912                    .display_ranges(&editor.display_snapshot(cx)),
  913                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  914            );
  915            assert!(pop_history(&mut editor, cx).is_none());
  916
  917            // Move the cursor a large distance via the mouse.
  918            // The history can jump back to the previous position.
  919            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  920            editor.end_selection(window, cx);
  921            assert_eq!(
  922                editor
  923                    .selections
  924                    .display_ranges(&editor.display_snapshot(cx)),
  925                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  926            );
  927            let nav_entry = pop_history(&mut editor, cx).unwrap();
  928            editor.navigate(nav_entry.data.unwrap(), window, cx);
  929            assert_eq!(nav_entry.item.id(), cx.entity_id());
  930            assert_eq!(
  931                editor
  932                    .selections
  933                    .display_ranges(&editor.display_snapshot(cx)),
  934                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  935            );
  936            assert!(pop_history(&mut editor, cx).is_none());
  937
  938            // Set scroll position to check later
  939            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  940            let original_scroll_position = editor
  941                .scroll_manager
  942                .native_anchor(&editor.display_snapshot(cx), cx);
  943
  944            // Jump to the end of the document and adjust scroll
  945            editor.move_to_end(&MoveToEnd, window, cx);
  946            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  947            assert_ne!(
  948                editor
  949                    .scroll_manager
  950                    .native_anchor(&editor.display_snapshot(cx), cx),
  951                original_scroll_position
  952            );
  953
  954            let nav_entry = pop_history(&mut editor, cx).unwrap();
  955            editor.navigate(nav_entry.data.unwrap(), window, cx);
  956            assert_eq!(
  957                editor
  958                    .scroll_manager
  959                    .native_anchor(&editor.display_snapshot(cx), cx),
  960                original_scroll_position
  961            );
  962
  963            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  964            let mut invalid_anchor = editor
  965                .scroll_manager
  966                .native_anchor(&editor.display_snapshot(cx), cx)
  967                .anchor;
  968            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  969            let invalid_point = Point::new(9999, 0);
  970            editor.navigate(
  971                Arc::new(NavigationData {
  972                    cursor_anchor: invalid_anchor,
  973                    cursor_position: invalid_point,
  974                    scroll_anchor: ScrollAnchor {
  975                        anchor: invalid_anchor,
  976                        offset: Default::default(),
  977                    },
  978                    scroll_top_row: invalid_point.row,
  979                }),
  980                window,
  981                cx,
  982            );
  983            assert_eq!(
  984                editor
  985                    .selections
  986                    .display_ranges(&editor.display_snapshot(cx)),
  987                &[editor.max_point(cx)..editor.max_point(cx)]
  988            );
  989            assert_eq!(
  990                editor.scroll_position(cx),
  991                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  992            );
  993
  994            editor
  995        })
  996    });
  997}
  998
  999#[gpui::test]
 1000fn test_cancel(cx: &mut TestAppContext) {
 1001    init_test(cx, |_| {});
 1002
 1003    let editor = cx.add_window(|window, cx| {
 1004        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 1005        build_editor(buffer, window, cx)
 1006    });
 1007
 1008    _ = editor.update(cx, |editor, window, cx| {
 1009        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
 1010        editor.update_selection(
 1011            DisplayPoint::new(DisplayRow(1), 1),
 1012            0,
 1013            gpui::Point::<f32>::default(),
 1014            window,
 1015            cx,
 1016        );
 1017        editor.end_selection(window, cx);
 1018
 1019        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1020        editor.update_selection(
 1021            DisplayPoint::new(DisplayRow(0), 3),
 1022            0,
 1023            gpui::Point::<f32>::default(),
 1024            window,
 1025            cx,
 1026        );
 1027        editor.end_selection(window, cx);
 1028        assert_eq!(
 1029            display_ranges(editor, cx),
 1030            [
 1031                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1032                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1033            ]
 1034        );
 1035    });
 1036
 1037    _ = editor.update(cx, |editor, window, cx| {
 1038        editor.cancel(&Cancel, window, cx);
 1039        assert_eq!(
 1040            display_ranges(editor, cx),
 1041            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1042        );
 1043    });
 1044
 1045    _ = editor.update(cx, |editor, window, cx| {
 1046        editor.cancel(&Cancel, window, cx);
 1047        assert_eq!(
 1048            display_ranges(editor, cx),
 1049            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1050        );
 1051    });
 1052}
 1053
 1054#[gpui::test]
 1055fn test_fold_action(cx: &mut TestAppContext) {
 1056    init_test(cx, |_| {});
 1057
 1058    let editor = cx.add_window(|window, cx| {
 1059        let buffer = MultiBuffer::build_simple(
 1060            &"
 1061                impl Foo {
 1062                    // Hello!
 1063
 1064                    fn a() {
 1065                        1
 1066                    }
 1067
 1068                    fn b() {
 1069                        2
 1070                    }
 1071
 1072                    fn c() {
 1073                        3
 1074                    }
 1075                }
 1076            "
 1077            .unindent(),
 1078            cx,
 1079        );
 1080        build_editor(buffer, window, cx)
 1081    });
 1082
 1083    _ = editor.update(cx, |editor, window, cx| {
 1084        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1085            s.select_display_ranges([
 1086                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1087            ]);
 1088        });
 1089        editor.fold(&Fold, window, cx);
 1090        assert_eq!(
 1091            editor.display_text(cx),
 1092            "
 1093                impl Foo {
 1094                    // Hello!
 1095
 1096                    fn a() {
 1097                        1
 1098                    }
 1099
 1100                    fn b() {⋯
 1101                    }
 1102
 1103                    fn c() {⋯
 1104                    }
 1105                }
 1106            "
 1107            .unindent(),
 1108        );
 1109
 1110        editor.fold(&Fold, window, cx);
 1111        assert_eq!(
 1112            editor.display_text(cx),
 1113            "
 1114                impl Foo {⋯
 1115                }
 1116            "
 1117            .unindent(),
 1118        );
 1119
 1120        editor.unfold_lines(&UnfoldLines, window, cx);
 1121        assert_eq!(
 1122            editor.display_text(cx),
 1123            "
 1124                impl Foo {
 1125                    // Hello!
 1126
 1127                    fn a() {
 1128                        1
 1129                    }
 1130
 1131                    fn b() {⋯
 1132                    }
 1133
 1134                    fn c() {⋯
 1135                    }
 1136                }
 1137            "
 1138            .unindent(),
 1139        );
 1140
 1141        editor.unfold_lines(&UnfoldLines, window, cx);
 1142        assert_eq!(
 1143            editor.display_text(cx),
 1144            editor.buffer.read(cx).read(cx).text()
 1145        );
 1146    });
 1147}
 1148
 1149#[gpui::test]
 1150fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1151    init_test(cx, |_| {});
 1152
 1153    let editor = cx.add_window(|window, cx| {
 1154        let buffer = MultiBuffer::build_simple(
 1155            &"
 1156                class Foo:
 1157                    # Hello!
 1158
 1159                    def a():
 1160                        print(1)
 1161
 1162                    def b():
 1163                        print(2)
 1164
 1165                    def c():
 1166                        print(3)
 1167            "
 1168            .unindent(),
 1169            cx,
 1170        );
 1171        build_editor(buffer, window, cx)
 1172    });
 1173
 1174    _ = editor.update(cx, |editor, window, cx| {
 1175        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1176            s.select_display_ranges([
 1177                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1178            ]);
 1179        });
 1180        editor.fold(&Fold, window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():
 1188                        print(1)
 1189
 1190                    def b():⋯
 1191
 1192                    def c():⋯
 1193            "
 1194            .unindent(),
 1195        );
 1196
 1197        editor.fold(&Fold, window, cx);
 1198        assert_eq!(
 1199            editor.display_text(cx),
 1200            "
 1201                class Foo:⋯
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.unfold_lines(&UnfoldLines, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218                    def c():⋯
 1219            "
 1220            .unindent(),
 1221        );
 1222
 1223        editor.unfold_lines(&UnfoldLines, window, cx);
 1224        assert_eq!(
 1225            editor.display_text(cx),
 1226            editor.buffer.read(cx).read(cx).text()
 1227        );
 1228    });
 1229}
 1230
 1231#[gpui::test]
 1232fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1233    init_test(cx, |_| {});
 1234
 1235    let editor = cx.add_window(|window, cx| {
 1236        let buffer = MultiBuffer::build_simple(
 1237            &"
 1238                class Foo:
 1239                    # Hello!
 1240
 1241                    def a():
 1242                        print(1)
 1243
 1244                    def b():
 1245                        print(2)
 1246
 1247
 1248                    def c():
 1249                        print(3)
 1250
 1251
 1252            "
 1253            .unindent(),
 1254            cx,
 1255        );
 1256        build_editor(buffer, window, cx)
 1257    });
 1258
 1259    _ = editor.update(cx, |editor, window, cx| {
 1260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1261            s.select_display_ranges([
 1262                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1263            ]);
 1264        });
 1265        editor.fold(&Fold, window, cx);
 1266        assert_eq!(
 1267            editor.display_text(cx),
 1268            "
 1269                class Foo:
 1270                    # Hello!
 1271
 1272                    def a():
 1273                        print(1)
 1274
 1275                    def b():⋯
 1276
 1277
 1278                    def c():⋯
 1279
 1280
 1281            "
 1282            .unindent(),
 1283        );
 1284
 1285        editor.fold(&Fold, window, cx);
 1286        assert_eq!(
 1287            editor.display_text(cx),
 1288            "
 1289                class Foo:⋯
 1290
 1291
 1292            "
 1293            .unindent(),
 1294        );
 1295
 1296        editor.unfold_lines(&UnfoldLines, window, cx);
 1297        assert_eq!(
 1298            editor.display_text(cx),
 1299            "
 1300                class Foo:
 1301                    # Hello!
 1302
 1303                    def a():
 1304                        print(1)
 1305
 1306                    def b():⋯
 1307
 1308
 1309                    def c():⋯
 1310
 1311
 1312            "
 1313            .unindent(),
 1314        );
 1315
 1316        editor.unfold_lines(&UnfoldLines, window, cx);
 1317        assert_eq!(
 1318            editor.display_text(cx),
 1319            editor.buffer.read(cx).read(cx).text()
 1320        );
 1321    });
 1322}
 1323
 1324#[gpui::test]
 1325fn test_fold_at_level(cx: &mut TestAppContext) {
 1326    init_test(cx, |_| {});
 1327
 1328    let editor = cx.add_window(|window, cx| {
 1329        let buffer = MultiBuffer::build_simple(
 1330            &"
 1331                class Foo:
 1332                    # Hello!
 1333
 1334                    def a():
 1335                        print(1)
 1336
 1337                    def b():
 1338                        print(2)
 1339
 1340
 1341                class Bar:
 1342                    # World!
 1343
 1344                    def a():
 1345                        print(1)
 1346
 1347                    def b():
 1348                        print(2)
 1349
 1350
 1351            "
 1352            .unindent(),
 1353            cx,
 1354        );
 1355        build_editor(buffer, window, cx)
 1356    });
 1357
 1358    _ = editor.update(cx, |editor, window, cx| {
 1359        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1360        assert_eq!(
 1361            editor.display_text(cx),
 1362            "
 1363                class Foo:
 1364                    # Hello!
 1365
 1366                    def a():⋯
 1367
 1368                    def b():⋯
 1369
 1370
 1371                class Bar:
 1372                    # World!
 1373
 1374                    def a():⋯
 1375
 1376                    def b():⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1384        assert_eq!(
 1385            editor.display_text(cx),
 1386            "
 1387                class Foo:⋯
 1388
 1389
 1390                class Bar:⋯
 1391
 1392
 1393            "
 1394            .unindent(),
 1395        );
 1396
 1397        editor.unfold_all(&UnfoldAll, window, cx);
 1398        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1399        assert_eq!(
 1400            editor.display_text(cx),
 1401            "
 1402                class Foo:
 1403                    # Hello!
 1404
 1405                    def a():
 1406                        print(1)
 1407
 1408                    def b():
 1409                        print(2)
 1410
 1411
 1412                class Bar:
 1413                    # World!
 1414
 1415                    def a():
 1416                        print(1)
 1417
 1418                    def b():
 1419                        print(2)
 1420
 1421
 1422            "
 1423            .unindent(),
 1424        );
 1425
 1426        assert_eq!(
 1427            editor.display_text(cx),
 1428            editor.buffer.read(cx).read(cx).text()
 1429        );
 1430        let (_, positions) = marked_text_ranges(
 1431            &"
 1432                       class Foo:
 1433                           # Hello!
 1434
 1435                           def a():
 1436                              print(1)
 1437
 1438                           def b():
 1439                               p«riˇ»nt(2)
 1440
 1441
 1442                       class Bar:
 1443                           # World!
 1444
 1445                           def a():
 1446                               «ˇprint(1)
 1447
 1448                           def b():
 1449                               print(2)»
 1450
 1451
 1452                   "
 1453            .unindent(),
 1454            true,
 1455        );
 1456
 1457        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1458            s.select_ranges(
 1459                positions
 1460                    .iter()
 1461                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1462            )
 1463        });
 1464
 1465        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1466        assert_eq!(
 1467            editor.display_text(cx),
 1468            "
 1469                class Foo:
 1470                    # Hello!
 1471
 1472                    def a():⋯
 1473
 1474                    def b():
 1475                        print(2)
 1476
 1477
 1478                class Bar:
 1479                    # World!
 1480
 1481                    def a():
 1482                        print(1)
 1483
 1484                    def b():
 1485                        print(2)
 1486
 1487
 1488            "
 1489            .unindent(),
 1490        );
 1491    });
 1492}
 1493
 1494#[gpui::test]
 1495fn test_move_cursor(cx: &mut TestAppContext) {
 1496    init_test(cx, |_| {});
 1497
 1498    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1499    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1500
 1501    buffer.update(cx, |buffer, cx| {
 1502        buffer.edit(
 1503            vec![
 1504                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1505                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1506            ],
 1507            None,
 1508            cx,
 1509        );
 1510    });
 1511    _ = editor.update(cx, |editor, window, cx| {
 1512        assert_eq!(
 1513            display_ranges(editor, cx),
 1514            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1515        );
 1516
 1517        editor.move_down(&MoveDown, window, cx);
 1518        assert_eq!(
 1519            display_ranges(editor, cx),
 1520            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1521        );
 1522
 1523        editor.move_right(&MoveRight, window, cx);
 1524        assert_eq!(
 1525            display_ranges(editor, cx),
 1526            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1527        );
 1528
 1529        editor.move_left(&MoveLeft, window, cx);
 1530        assert_eq!(
 1531            display_ranges(editor, cx),
 1532            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1533        );
 1534
 1535        editor.move_up(&MoveUp, window, cx);
 1536        assert_eq!(
 1537            display_ranges(editor, cx),
 1538            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1539        );
 1540
 1541        editor.move_to_end(&MoveToEnd, window, cx);
 1542        assert_eq!(
 1543            display_ranges(editor, cx),
 1544            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1545        );
 1546
 1547        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1548        assert_eq!(
 1549            display_ranges(editor, cx),
 1550            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1551        );
 1552
 1553        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1554            s.select_display_ranges([
 1555                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1556            ]);
 1557        });
 1558        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1559        assert_eq!(
 1560            display_ranges(editor, cx),
 1561            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1562        );
 1563
 1564        editor.select_to_end(&SelectToEnd, window, cx);
 1565        assert_eq!(
 1566            display_ranges(editor, cx),
 1567            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1568        );
 1569    });
 1570}
 1571
 1572#[gpui::test]
 1573fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1574    init_test(cx, |_| {});
 1575
 1576    let editor = cx.add_window(|window, cx| {
 1577        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1578        build_editor(buffer, window, cx)
 1579    });
 1580
 1581    assert_eq!('🟥'.len_utf8(), 4);
 1582    assert_eq!('α'.len_utf8(), 2);
 1583
 1584    _ = editor.update(cx, |editor, window, cx| {
 1585        editor.fold_creases(
 1586            vec![
 1587                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1588                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1589                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1590            ],
 1591            true,
 1592            window,
 1593            cx,
 1594        );
 1595        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1596
 1597        editor.move_right(&MoveRight, window, cx);
 1598        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1599        editor.move_right(&MoveRight, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1603
 1604        editor.move_down(&MoveDown, window, cx);
 1605        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1606        editor.move_left(&MoveLeft, window, cx);
 1607        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1608        editor.move_left(&MoveLeft, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1610        editor.move_left(&MoveLeft, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1612
 1613        editor.move_down(&MoveDown, window, cx);
 1614        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1615        editor.move_right(&MoveRight, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1617        editor.move_right(&MoveRight, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1619        editor.move_right(&MoveRight, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1621
 1622        editor.move_up(&MoveUp, window, cx);
 1623        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1624        editor.move_down(&MoveDown, window, cx);
 1625        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1626        editor.move_up(&MoveUp, window, cx);
 1627        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1628
 1629        editor.move_up(&MoveUp, window, cx);
 1630        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1631        editor.move_left(&MoveLeft, window, cx);
 1632        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1633        editor.move_left(&MoveLeft, window, cx);
 1634        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1635    });
 1636}
 1637
 1638#[gpui::test]
 1639fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1640    init_test(cx, |_| {});
 1641
 1642    let editor = cx.add_window(|window, cx| {
 1643        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1644        build_editor(buffer, window, cx)
 1645    });
 1646    _ = editor.update(cx, |editor, window, cx| {
 1647        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1648            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1649        });
 1650
 1651        // moving above start of document should move selection to start of document,
 1652        // but the next move down should still be at the original goal_x
 1653        editor.move_up(&MoveUp, window, cx);
 1654        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1655
 1656        editor.move_down(&MoveDown, window, cx);
 1657        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1661
 1662        editor.move_down(&MoveDown, window, cx);
 1663        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1667
 1668        // moving past end of document should not change goal_x
 1669        editor.move_down(&MoveDown, window, cx);
 1670        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1671
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1674
 1675        editor.move_up(&MoveUp, window, cx);
 1676        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1677
 1678        editor.move_up(&MoveUp, window, cx);
 1679        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1680
 1681        editor.move_up(&MoveUp, window, cx);
 1682        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1683    });
 1684}
 1685
 1686#[gpui::test]
 1687fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1688    init_test(cx, |_| {});
 1689    let move_to_beg = MoveToBeginningOfLine {
 1690        stop_at_soft_wraps: true,
 1691        stop_at_indent: true,
 1692    };
 1693
 1694    let delete_to_beg = DeleteToBeginningOfLine {
 1695        stop_at_indent: false,
 1696    };
 1697
 1698    let move_to_end = MoveToEndOfLine {
 1699        stop_at_soft_wraps: true,
 1700    };
 1701
 1702    let editor = cx.add_window(|window, cx| {
 1703        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1704        build_editor(buffer, window, cx)
 1705    });
 1706    _ = editor.update(cx, |editor, window, cx| {
 1707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1708            s.select_display_ranges([
 1709                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1710                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1711            ]);
 1712        });
 1713    });
 1714
 1715    _ = editor.update(cx, |editor, window, cx| {
 1716        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1717        assert_eq!(
 1718            display_ranges(editor, cx),
 1719            &[
 1720                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1721                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1722            ]
 1723        );
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1728        assert_eq!(
 1729            display_ranges(editor, cx),
 1730            &[
 1731                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1732                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1733            ]
 1734        );
 1735    });
 1736
 1737    _ = editor.update(cx, |editor, window, cx| {
 1738        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1739        assert_eq!(
 1740            display_ranges(editor, cx),
 1741            &[
 1742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1743                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1744            ]
 1745        );
 1746    });
 1747
 1748    _ = editor.update(cx, |editor, window, cx| {
 1749        editor.move_to_end_of_line(&move_to_end, window, cx);
 1750        assert_eq!(
 1751            display_ranges(editor, cx),
 1752            &[
 1753                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1754                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1755            ]
 1756        );
 1757    });
 1758
 1759    // Moving to the end of line again is a no-op.
 1760    _ = editor.update(cx, |editor, window, cx| {
 1761        editor.move_to_end_of_line(&move_to_end, window, cx);
 1762        assert_eq!(
 1763            display_ranges(editor, cx),
 1764            &[
 1765                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1766                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1767            ]
 1768        );
 1769    });
 1770
 1771    _ = editor.update(cx, |editor, window, cx| {
 1772        editor.move_left(&MoveLeft, window, cx);
 1773        editor.select_to_beginning_of_line(
 1774            &SelectToBeginningOfLine {
 1775                stop_at_soft_wraps: true,
 1776                stop_at_indent: true,
 1777            },
 1778            window,
 1779            cx,
 1780        );
 1781        assert_eq!(
 1782            display_ranges(editor, cx),
 1783            &[
 1784                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1785                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1786            ]
 1787        );
 1788    });
 1789
 1790    _ = editor.update(cx, |editor, window, cx| {
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            display_ranges(editor, cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            display_ranges(editor, cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_end_of_line(
 1828            &SelectToEndOfLine {
 1829                stop_at_soft_wraps: true,
 1830            },
 1831            window,
 1832            cx,
 1833        );
 1834        assert_eq!(
 1835            display_ranges(editor, cx),
 1836            &[
 1837                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1838                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1839            ]
 1840        );
 1841    });
 1842
 1843    _ = editor.update(cx, |editor, window, cx| {
 1844        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1845        assert_eq!(editor.display_text(cx), "ab\n  de");
 1846        assert_eq!(
 1847            display_ranges(editor, cx),
 1848            &[
 1849                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1850                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1851            ]
 1852        );
 1853    });
 1854
 1855    _ = editor.update(cx, |editor, window, cx| {
 1856        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1857        assert_eq!(editor.display_text(cx), "\n");
 1858        assert_eq!(
 1859            display_ranges(editor, cx),
 1860            &[
 1861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1862                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1863            ]
 1864        );
 1865    });
 1866}
 1867
 1868#[gpui::test]
 1869fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1870    init_test(cx, |_| {});
 1871    let move_to_beg = MoveToBeginningOfLine {
 1872        stop_at_soft_wraps: false,
 1873        stop_at_indent: false,
 1874    };
 1875
 1876    let move_to_end = MoveToEndOfLine {
 1877        stop_at_soft_wraps: false,
 1878    };
 1879
 1880    let editor = cx.add_window(|window, cx| {
 1881        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1882        build_editor(buffer, window, cx)
 1883    });
 1884
 1885    _ = editor.update(cx, |editor, window, cx| {
 1886        editor.set_wrap_width(Some(140.0.into()), cx);
 1887
 1888        // We expect the following lines after wrapping
 1889        // ```
 1890        // thequickbrownfox
 1891        // jumpedoverthelazydo
 1892        // gs
 1893        // ```
 1894        // The final `gs` was soft-wrapped onto a new line.
 1895        assert_eq!(
 1896            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1897            editor.display_text(cx),
 1898        );
 1899
 1900        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1901        // Start the cursor at the `k` on the first line
 1902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1903            s.select_display_ranges([
 1904                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1905            ]);
 1906        });
 1907
 1908        // Moving to the beginning of the line should put us at the beginning of the line.
 1909        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1910        assert_eq!(
 1911            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1912            display_ranges(editor, cx)
 1913        );
 1914
 1915        // Moving to the end of the line should put us at the end of the line.
 1916        editor.move_to_end_of_line(&move_to_end, window, cx);
 1917        assert_eq!(
 1918            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1919            display_ranges(editor, cx)
 1920        );
 1921
 1922        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1923        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1925            s.select_display_ranges([
 1926                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1927            ]);
 1928        });
 1929
 1930        // Moving to the beginning of the line should put us at the start of the second line of
 1931        // display text, i.e., the `j`.
 1932        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1933        assert_eq!(
 1934            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1935            display_ranges(editor, cx)
 1936        );
 1937
 1938        // Moving to the beginning of the line again should be a no-op.
 1939        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1940        assert_eq!(
 1941            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1942            display_ranges(editor, cx)
 1943        );
 1944
 1945        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1946        // next display line.
 1947        editor.move_to_end_of_line(&move_to_end, window, cx);
 1948        assert_eq!(
 1949            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1950            display_ranges(editor, cx)
 1951        );
 1952
 1953        // Moving to the end of the line again should be a no-op.
 1954        editor.move_to_end_of_line(&move_to_end, window, cx);
 1955        assert_eq!(
 1956            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1957            display_ranges(editor, cx)
 1958        );
 1959    });
 1960}
 1961
 1962#[gpui::test]
 1963fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1964    init_test(cx, |_| {});
 1965
 1966    let move_to_beg = MoveToBeginningOfLine {
 1967        stop_at_soft_wraps: true,
 1968        stop_at_indent: true,
 1969    };
 1970
 1971    let select_to_beg = SelectToBeginningOfLine {
 1972        stop_at_soft_wraps: true,
 1973        stop_at_indent: true,
 1974    };
 1975
 1976    let delete_to_beg = DeleteToBeginningOfLine {
 1977        stop_at_indent: true,
 1978    };
 1979
 1980    let move_to_end = MoveToEndOfLine {
 1981        stop_at_soft_wraps: false,
 1982    };
 1983
 1984    let editor = cx.add_window(|window, cx| {
 1985        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1986        build_editor(buffer, window, cx)
 1987    });
 1988
 1989    _ = editor.update(cx, |editor, window, cx| {
 1990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1991            s.select_display_ranges([
 1992                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1993                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1994            ]);
 1995        });
 1996
 1997        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1998        // and the second cursor at the first non-whitespace character in the line.
 1999        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2000        assert_eq!(
 2001            display_ranges(editor, cx),
 2002            &[
 2003                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2004                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2005            ]
 2006        );
 2007
 2008        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2009        // and should move the second cursor to the beginning of the line.
 2010        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2011        assert_eq!(
 2012            display_ranges(editor, cx),
 2013            &[
 2014                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2015                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2016            ]
 2017        );
 2018
 2019        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2020        // and should move the second cursor back to the first non-whitespace character in the line.
 2021        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2022        assert_eq!(
 2023            display_ranges(editor, cx),
 2024            &[
 2025                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2026                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2027            ]
 2028        );
 2029
 2030        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2031        // and to the first non-whitespace character in the line for the second cursor.
 2032        editor.move_to_end_of_line(&move_to_end, window, cx);
 2033        editor.move_left(&MoveLeft, window, cx);
 2034        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2035        assert_eq!(
 2036            display_ranges(editor, cx),
 2037            &[
 2038                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2039                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2040            ]
 2041        );
 2042
 2043        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2044        // and should select to the beginning of the line for the second cursor.
 2045        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2046        assert_eq!(
 2047            display_ranges(editor, cx),
 2048            &[
 2049                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2050                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2051            ]
 2052        );
 2053
 2054        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2055        // and should delete to the first non-whitespace character in the line for the second cursor.
 2056        editor.move_to_end_of_line(&move_to_end, window, cx);
 2057        editor.move_left(&MoveLeft, window, cx);
 2058        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2059        assert_eq!(editor.text(cx), "c\n  f");
 2060    });
 2061}
 2062
 2063#[gpui::test]
 2064fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2065    init_test(cx, |_| {});
 2066
 2067    let move_to_beg = MoveToBeginningOfLine {
 2068        stop_at_soft_wraps: true,
 2069        stop_at_indent: true,
 2070    };
 2071
 2072    let editor = cx.add_window(|window, cx| {
 2073        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2074        build_editor(buffer, window, cx)
 2075    });
 2076
 2077    _ = editor.update(cx, |editor, window, cx| {
 2078        // test cursor between line_start and indent_start
 2079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2080            s.select_display_ranges([
 2081                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2082            ]);
 2083        });
 2084
 2085        // cursor should move to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091
 2092        // cursor should move to indent_start
 2093        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2094        assert_eq!(
 2095            display_ranges(editor, cx),
 2096            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2097        );
 2098
 2099        // cursor should move to back to line_start
 2100        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2101        assert_eq!(
 2102            display_ranges(editor, cx),
 2103            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2104        );
 2105    });
 2106}
 2107
 2108#[gpui::test]
 2109fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2110    init_test(cx, |_| {});
 2111
 2112    let editor = cx.add_window(|window, cx| {
 2113        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2114        build_editor(buffer, window, cx)
 2115    });
 2116    _ = editor.update(cx, |editor, window, cx| {
 2117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2118            s.select_display_ranges([
 2119                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2120                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2121            ])
 2122        });
 2123        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2124        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2125
 2126        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2127        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2128
 2129        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2130        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2131
 2132        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2133        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2134
 2135        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2136        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2137
 2138        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2139        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2140
 2141        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2142        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2143
 2144        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2145        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_right(&MoveRight, window, cx);
 2148        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154
 2155        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2156        assert_selection_ranges(
 2157            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2158            editor,
 2159            cx,
 2160        );
 2161
 2162        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2163        assert_selection_ranges(
 2164            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2165            editor,
 2166            cx,
 2167        );
 2168    });
 2169}
 2170
 2171#[gpui::test]
 2172fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2173    init_test(cx, |_| {});
 2174
 2175    let editor = cx.add_window(|window, cx| {
 2176        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2177        build_editor(buffer, window, cx)
 2178    });
 2179
 2180    _ = editor.update(cx, |editor, window, cx| {
 2181        editor.set_wrap_width(Some(140.0.into()), cx);
 2182        assert_eq!(
 2183            editor.display_text(cx),
 2184            "use one::{\n    two::three::\n    four::five\n};"
 2185        );
 2186
 2187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2188            s.select_display_ranges([
 2189                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2190            ]);
 2191        });
 2192
 2193        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2194        assert_eq!(
 2195            display_ranges(editor, cx),
 2196            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2197        );
 2198
 2199        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2200        assert_eq!(
 2201            display_ranges(editor, cx),
 2202            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2203        );
 2204
 2205        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2206        assert_eq!(
 2207            display_ranges(editor, cx),
 2208            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2209        );
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            display_ranges(editor, cx),
 2214            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2215        );
 2216
 2217        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2218        assert_eq!(
 2219            display_ranges(editor, cx),
 2220            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2221        );
 2222
 2223        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2224        assert_eq!(
 2225            display_ranges(editor, cx),
 2226            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2227        );
 2228    });
 2229}
 2230
 2231#[gpui::test]
 2232async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2233    init_test(cx, |_| {});
 2234    let mut cx = EditorTestContext::new(cx).await;
 2235
 2236    let line_height = cx.update_editor(|editor, window, cx| {
 2237        editor
 2238            .style(cx)
 2239            .text
 2240            .line_height_in_pixels(window.rem_size())
 2241    });
 2242    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2243
 2244    cx.set_state(
 2245        &r#"ˇone
 2246        two
 2247
 2248        three
 2249        fourˇ
 2250        five
 2251
 2252        six"#
 2253            .unindent(),
 2254    );
 2255
 2256    cx.update_editor(|editor, window, cx| {
 2257        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2258    });
 2259    cx.assert_editor_state(
 2260        &r#"one
 2261        two
 2262        ˇ
 2263        three
 2264        four
 2265        five
 2266        ˇ
 2267        six"#
 2268            .unindent(),
 2269    );
 2270
 2271    cx.update_editor(|editor, window, cx| {
 2272        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2273    });
 2274    cx.assert_editor_state(
 2275        &r#"one
 2276        two
 2277
 2278        three
 2279        four
 2280        five
 2281        ˇ
 2282        sixˇ"#
 2283            .unindent(),
 2284    );
 2285
 2286    cx.update_editor(|editor, window, cx| {
 2287        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2288    });
 2289    cx.assert_editor_state(
 2290        &r#"one
 2291        two
 2292
 2293        three
 2294        four
 2295        five
 2296
 2297        sixˇ"#
 2298            .unindent(),
 2299    );
 2300
 2301    cx.update_editor(|editor, window, cx| {
 2302        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2303    });
 2304    cx.assert_editor_state(
 2305        &r#"one
 2306        two
 2307
 2308        three
 2309        four
 2310        five
 2311        ˇ
 2312        six"#
 2313            .unindent(),
 2314    );
 2315
 2316    cx.update_editor(|editor, window, cx| {
 2317        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2318    });
 2319    cx.assert_editor_state(
 2320        &r#"one
 2321        two
 2322        ˇ
 2323        three
 2324        four
 2325        five
 2326
 2327        six"#
 2328            .unindent(),
 2329    );
 2330
 2331    cx.update_editor(|editor, window, cx| {
 2332        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2333    });
 2334    cx.assert_editor_state(
 2335        &r#"ˇone
 2336        two
 2337
 2338        three
 2339        four
 2340        five
 2341
 2342        six"#
 2343            .unindent(),
 2344    );
 2345}
 2346
 2347#[gpui::test]
 2348async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2349    init_test(cx, |_| {});
 2350    let mut cx = EditorTestContext::new(cx).await;
 2351    let line_height = cx.update_editor(|editor, window, cx| {
 2352        editor
 2353            .style(cx)
 2354            .text
 2355            .line_height_in_pixels(window.rem_size())
 2356    });
 2357    let window = cx.window;
 2358    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2359
 2360    cx.set_state(
 2361        r#"ˇone
 2362        two
 2363        three
 2364        four
 2365        five
 2366        six
 2367        seven
 2368        eight
 2369        nine
 2370        ten
 2371        "#,
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| {
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 0.)
 2378        );
 2379        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2380        assert_eq!(
 2381            editor.snapshot(window, cx).scroll_position(),
 2382            gpui::Point::new(0., 3.)
 2383        );
 2384        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2385        assert_eq!(
 2386            editor.snapshot(window, cx).scroll_position(),
 2387            gpui::Point::new(0., 6.)
 2388        );
 2389        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2390        assert_eq!(
 2391            editor.snapshot(window, cx).scroll_position(),
 2392            gpui::Point::new(0., 3.)
 2393        );
 2394
 2395        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2396        assert_eq!(
 2397            editor.snapshot(window, cx).scroll_position(),
 2398            gpui::Point::new(0., 1.)
 2399        );
 2400        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2401        assert_eq!(
 2402            editor.snapshot(window, cx).scroll_position(),
 2403            gpui::Point::new(0., 3.)
 2404        );
 2405    });
 2406}
 2407
 2408#[gpui::test]
 2409async fn test_autoscroll(cx: &mut TestAppContext) {
 2410    init_test(cx, |_| {});
 2411    let mut cx = EditorTestContext::new(cx).await;
 2412
 2413    let line_height = cx.update_editor(|editor, window, cx| {
 2414        editor.set_vertical_scroll_margin(2, cx);
 2415        editor
 2416            .style(cx)
 2417            .text
 2418            .line_height_in_pixels(window.rem_size())
 2419    });
 2420    let window = cx.window;
 2421    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2422
 2423    cx.set_state(
 2424        r#"ˇone
 2425            two
 2426            three
 2427            four
 2428            five
 2429            six
 2430            seven
 2431            eight
 2432            nine
 2433            ten
 2434        "#,
 2435    );
 2436    cx.update_editor(|editor, window, cx| {
 2437        assert_eq!(
 2438            editor.snapshot(window, cx).scroll_position(),
 2439            gpui::Point::new(0., 0.0)
 2440        );
 2441    });
 2442
 2443    // Add a cursor below the visible area. Since both cursors cannot fit
 2444    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2445    // allows the vertical scroll margin below that cursor.
 2446    cx.update_editor(|editor, window, cx| {
 2447        editor.change_selections(Default::default(), window, cx, |selections| {
 2448            selections.select_ranges([
 2449                Point::new(0, 0)..Point::new(0, 0),
 2450                Point::new(6, 0)..Point::new(6, 0),
 2451            ]);
 2452        })
 2453    });
 2454    cx.update_editor(|editor, window, cx| {
 2455        assert_eq!(
 2456            editor.snapshot(window, cx).scroll_position(),
 2457            gpui::Point::new(0., 3.0)
 2458        );
 2459    });
 2460
 2461    // Move down. The editor cursor scrolls down to track the newest cursor.
 2462    cx.update_editor(|editor, window, cx| {
 2463        editor.move_down(&Default::default(), window, cx);
 2464    });
 2465    cx.update_editor(|editor, window, cx| {
 2466        assert_eq!(
 2467            editor.snapshot(window, cx).scroll_position(),
 2468            gpui::Point::new(0., 4.0)
 2469        );
 2470    });
 2471
 2472    // Add a cursor above the visible area. Since both cursors fit on screen,
 2473    // the editor scrolls to show both.
 2474    cx.update_editor(|editor, window, cx| {
 2475        editor.change_selections(Default::default(), window, cx, |selections| {
 2476            selections.select_ranges([
 2477                Point::new(1, 0)..Point::new(1, 0),
 2478                Point::new(6, 0)..Point::new(6, 0),
 2479            ]);
 2480        })
 2481    });
 2482    cx.update_editor(|editor, window, cx| {
 2483        assert_eq!(
 2484            editor.snapshot(window, cx).scroll_position(),
 2485            gpui::Point::new(0., 1.0)
 2486        );
 2487    });
 2488}
 2489
 2490#[gpui::test]
 2491async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2492    init_test(cx, |_| {});
 2493    let mut cx = EditorTestContext::new(cx).await;
 2494
 2495    let line_height = cx.update_editor(|editor, window, cx| {
 2496        editor
 2497            .style(cx)
 2498            .text
 2499            .line_height_in_pixels(window.rem_size())
 2500    });
 2501    let window = cx.window;
 2502    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2503    cx.set_state(
 2504        &r#"
 2505        ˇone
 2506        two
 2507        threeˇ
 2508        four
 2509        five
 2510        six
 2511        seven
 2512        eight
 2513        nine
 2514        ten
 2515        "#
 2516        .unindent(),
 2517    );
 2518
 2519    cx.update_editor(|editor, window, cx| {
 2520        editor.move_page_down(&MovePageDown::default(), window, cx)
 2521    });
 2522    cx.assert_editor_state(
 2523        &r#"
 2524        one
 2525        two
 2526        three
 2527        ˇfour
 2528        five
 2529        sixˇ
 2530        seven
 2531        eight
 2532        nine
 2533        ten
 2534        "#
 2535        .unindent(),
 2536    );
 2537
 2538    cx.update_editor(|editor, window, cx| {
 2539        editor.move_page_down(&MovePageDown::default(), window, cx)
 2540    });
 2541    cx.assert_editor_state(
 2542        &r#"
 2543        one
 2544        two
 2545        three
 2546        four
 2547        five
 2548        six
 2549        ˇseven
 2550        eight
 2551        nineˇ
 2552        ten
 2553        "#
 2554        .unindent(),
 2555    );
 2556
 2557    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2558    cx.assert_editor_state(
 2559        &r#"
 2560        one
 2561        two
 2562        three
 2563        ˇfour
 2564        five
 2565        sixˇ
 2566        seven
 2567        eight
 2568        nine
 2569        ten
 2570        "#
 2571        .unindent(),
 2572    );
 2573
 2574    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2575    cx.assert_editor_state(
 2576        &r#"
 2577        ˇone
 2578        two
 2579        threeˇ
 2580        four
 2581        five
 2582        six
 2583        seven
 2584        eight
 2585        nine
 2586        ten
 2587        "#
 2588        .unindent(),
 2589    );
 2590
 2591    // Test select collapsing
 2592    cx.update_editor(|editor, window, cx| {
 2593        editor.move_page_down(&MovePageDown::default(), window, cx);
 2594        editor.move_page_down(&MovePageDown::default(), window, cx);
 2595        editor.move_page_down(&MovePageDown::default(), window, cx);
 2596    });
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        one
 2600        two
 2601        three
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ˇten
 2609        ˇ"#
 2610        .unindent(),
 2611    );
 2612}
 2613
 2614#[gpui::test]
 2615async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2616    init_test(cx, |_| {});
 2617    let mut cx = EditorTestContext::new(cx).await;
 2618    cx.set_state("one «two threeˇ» four");
 2619    cx.update_editor(|editor, window, cx| {
 2620        editor.delete_to_beginning_of_line(
 2621            &DeleteToBeginningOfLine {
 2622                stop_at_indent: false,
 2623            },
 2624            window,
 2625            cx,
 2626        );
 2627        assert_eq!(editor.text(cx), " four");
 2628    });
 2629}
 2630
 2631#[gpui::test]
 2632async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2633    init_test(cx, |_| {});
 2634
 2635    let mut cx = EditorTestContext::new(cx).await;
 2636
 2637    // For an empty selection, the preceding word fragment is deleted.
 2638    // For non-empty selections, only selected characters are deleted.
 2639    cx.set_state("onˇe two t«hreˇ»e four");
 2640    cx.update_editor(|editor, window, cx| {
 2641        editor.delete_to_previous_word_start(
 2642            &DeleteToPreviousWordStart {
 2643                ignore_newlines: false,
 2644                ignore_brackets: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649    });
 2650    cx.assert_editor_state("ˇe two tˇe four");
 2651
 2652    cx.set_state("e tˇwo te «fˇ»our");
 2653    cx.update_editor(|editor, window, cx| {
 2654        editor.delete_to_next_word_end(
 2655            &DeleteToNextWordEnd {
 2656                ignore_newlines: false,
 2657                ignore_brackets: false,
 2658            },
 2659            window,
 2660            cx,
 2661        );
 2662    });
 2663    cx.assert_editor_state("e tˇ te ˇour");
 2664}
 2665
 2666#[gpui::test]
 2667async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2668    init_test(cx, |_| {});
 2669
 2670    let mut cx = EditorTestContext::new(cx).await;
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: true,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2684    cx.assert_editor_state("here is some textˇwith a space");
 2685
 2686    cx.set_state("here is some text    ˇwith a space");
 2687    cx.update_editor(|editor, window, cx| {
 2688        editor.delete_to_previous_word_start(
 2689            &DeleteToPreviousWordStart {
 2690                ignore_newlines: false,
 2691                ignore_brackets: false,
 2692            },
 2693            window,
 2694            cx,
 2695        );
 2696    });
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: true,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    // Same happens in the other direction.
 2711    cx.assert_editor_state("here is some textˇwith a space");
 2712
 2713    cx.set_state("here is some textˇ    with a space");
 2714    cx.update_editor(|editor, window, cx| {
 2715        editor.delete_to_next_word_end(
 2716            &DeleteToNextWordEnd {
 2717                ignore_newlines: false,
 2718                ignore_brackets: false,
 2719            },
 2720            window,
 2721            cx,
 2722        );
 2723    });
 2724    cx.assert_editor_state("here is some textˇwith a space");
 2725
 2726    cx.set_state("here is some textˇ    with a space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_next_word_end(
 2729            &DeleteToNextWordEnd {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("here is some textˇwith a space");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_previous_word_start(
 2740            &DeleteToPreviousWordStart {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    cx.assert_editor_state("here is some ˇwith a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_previous_word_start(
 2751            &DeleteToPreviousWordStart {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    // Single whitespaces are removed with the word behind them.
 2760    cx.assert_editor_state("here is ˇwith a space");
 2761    cx.update_editor(|editor, window, cx| {
 2762        editor.delete_to_previous_word_start(
 2763            &DeleteToPreviousWordStart {
 2764                ignore_newlines: true,
 2765                ignore_brackets: false,
 2766            },
 2767            window,
 2768            cx,
 2769        );
 2770    });
 2771    cx.assert_editor_state("here ˇwith a space");
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.delete_to_previous_word_start(
 2774            &DeleteToPreviousWordStart {
 2775                ignore_newlines: true,
 2776                ignore_brackets: false,
 2777            },
 2778            window,
 2779            cx,
 2780        );
 2781    });
 2782    cx.assert_editor_state("ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_next_word_end(
 2796            &DeleteToNextWordEnd {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    // Same happens in the other direction.
 2805    cx.assert_editor_state("ˇ a space");
 2806    cx.update_editor(|editor, window, cx| {
 2807        editor.delete_to_next_word_end(
 2808            &DeleteToNextWordEnd {
 2809                ignore_newlines: true,
 2810                ignore_brackets: false,
 2811            },
 2812            window,
 2813            cx,
 2814        );
 2815    });
 2816    cx.assert_editor_state("ˇ space");
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_next_word_end(
 2819            &DeleteToNextWordEnd {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    cx.assert_editor_state("ˇ");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_previous_word_start(
 2841            &DeleteToPreviousWordStart {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850}
 2851
 2852#[gpui::test]
 2853async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2854    init_test(cx, |_| {});
 2855
 2856    let language = Arc::new(
 2857        Language::new(
 2858            LanguageConfig {
 2859                brackets: BracketPairConfig {
 2860                    pairs: vec![
 2861                        BracketPair {
 2862                            start: "\"".to_string(),
 2863                            end: "\"".to_string(),
 2864                            close: true,
 2865                            surround: true,
 2866                            newline: false,
 2867                        },
 2868                        BracketPair {
 2869                            start: "(".to_string(),
 2870                            end: ")".to_string(),
 2871                            close: true,
 2872                            surround: true,
 2873                            newline: true,
 2874                        },
 2875                    ],
 2876                    ..BracketPairConfig::default()
 2877                },
 2878                ..LanguageConfig::default()
 2879            },
 2880            Some(tree_sitter_rust::LANGUAGE.into()),
 2881        )
 2882        .with_brackets_query(
 2883            r#"
 2884                ("(" @open ")" @close)
 2885                ("\"" @open "\"" @close)
 2886            "#,
 2887        )
 2888        .unwrap(),
 2889    );
 2890
 2891    let mut cx = EditorTestContext::new(cx).await;
 2892    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2893
 2894    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2895    cx.update_editor(|editor, window, cx| {
 2896        editor.delete_to_previous_word_start(
 2897            &DeleteToPreviousWordStart {
 2898                ignore_newlines: true,
 2899                ignore_brackets: false,
 2900            },
 2901            window,
 2902            cx,
 2903        );
 2904    });
 2905    // Deletion stops before brackets if asked to not ignore them.
 2906    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2907    cx.update_editor(|editor, window, cx| {
 2908        editor.delete_to_previous_word_start(
 2909            &DeleteToPreviousWordStart {
 2910                ignore_newlines: true,
 2911                ignore_brackets: false,
 2912            },
 2913            window,
 2914            cx,
 2915        );
 2916    });
 2917    // Deletion has to remove a single bracket and then stop again.
 2918    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2919
 2920    cx.update_editor(|editor, window, cx| {
 2921        editor.delete_to_previous_word_start(
 2922            &DeleteToPreviousWordStart {
 2923                ignore_newlines: true,
 2924                ignore_brackets: false,
 2925            },
 2926            window,
 2927            cx,
 2928        );
 2929    });
 2930    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2931
 2932    cx.update_editor(|editor, window, cx| {
 2933        editor.delete_to_previous_word_start(
 2934            &DeleteToPreviousWordStart {
 2935                ignore_newlines: true,
 2936                ignore_brackets: false,
 2937            },
 2938            window,
 2939            cx,
 2940        );
 2941    });
 2942    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2943
 2944    cx.update_editor(|editor, window, cx| {
 2945        editor.delete_to_previous_word_start(
 2946            &DeleteToPreviousWordStart {
 2947                ignore_newlines: true,
 2948                ignore_brackets: false,
 2949            },
 2950            window,
 2951            cx,
 2952        );
 2953    });
 2954    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2955
 2956    cx.update_editor(|editor, window, cx| {
 2957        editor.delete_to_next_word_end(
 2958            &DeleteToNextWordEnd {
 2959                ignore_newlines: true,
 2960                ignore_brackets: false,
 2961            },
 2962            window,
 2963            cx,
 2964        );
 2965    });
 2966    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2967    cx.assert_editor_state(r#"ˇ");"#);
 2968
 2969    cx.update_editor(|editor, window, cx| {
 2970        editor.delete_to_next_word_end(
 2971            &DeleteToNextWordEnd {
 2972                ignore_newlines: true,
 2973                ignore_brackets: false,
 2974            },
 2975            window,
 2976            cx,
 2977        );
 2978    });
 2979    cx.assert_editor_state(r#"ˇ"#);
 2980
 2981    cx.update_editor(|editor, window, cx| {
 2982        editor.delete_to_next_word_end(
 2983            &DeleteToNextWordEnd {
 2984                ignore_newlines: true,
 2985                ignore_brackets: false,
 2986            },
 2987            window,
 2988            cx,
 2989        );
 2990    });
 2991    cx.assert_editor_state(r#"ˇ"#);
 2992
 2993    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2994    cx.update_editor(|editor, window, cx| {
 2995        editor.delete_to_previous_word_start(
 2996            &DeleteToPreviousWordStart {
 2997                ignore_newlines: true,
 2998                ignore_brackets: true,
 2999            },
 3000            window,
 3001            cx,
 3002        );
 3003    });
 3004    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3005}
 3006
 3007#[gpui::test]
 3008fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3009    init_test(cx, |_| {});
 3010
 3011    let editor = cx.add_window(|window, cx| {
 3012        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3013        build_editor(buffer, window, cx)
 3014    });
 3015    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3016        ignore_newlines: false,
 3017        ignore_brackets: false,
 3018    };
 3019    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3020        ignore_newlines: true,
 3021        ignore_brackets: false,
 3022    };
 3023
 3024    _ = editor.update(cx, |editor, window, cx| {
 3025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3026            s.select_display_ranges([
 3027                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3028            ])
 3029        });
 3030        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3031        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3032        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3033        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3034        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3035        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3036        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3037        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3038        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3039        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3040        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3041        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3042    });
 3043}
 3044
 3045#[gpui::test]
 3046fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3047    init_test(cx, |_| {});
 3048
 3049    let editor = cx.add_window(|window, cx| {
 3050        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3051        build_editor(buffer, window, cx)
 3052    });
 3053    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3054        ignore_newlines: false,
 3055        ignore_brackets: false,
 3056    };
 3057    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 3058        ignore_newlines: true,
 3059        ignore_brackets: false,
 3060    };
 3061
 3062    _ = editor.update(cx, |editor, window, cx| {
 3063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3064            s.select_display_ranges([
 3065                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3066            ])
 3067        });
 3068        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3069        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3070        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3071        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3072        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3074        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3075        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3076        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3077        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3078        editor.delete_to_previous_subword_start(
 3079            &del_to_prev_sub_word_start_ignore_newlines,
 3080            window,
 3081            cx,
 3082        );
 3083        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3084    });
 3085}
 3086
 3087#[gpui::test]
 3088fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3089    init_test(cx, |_| {});
 3090
 3091    let editor = cx.add_window(|window, cx| {
 3092        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3093        build_editor(buffer, window, cx)
 3094    });
 3095    let del_to_next_word_end = DeleteToNextWordEnd {
 3096        ignore_newlines: false,
 3097        ignore_brackets: false,
 3098    };
 3099    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3100        ignore_newlines: true,
 3101        ignore_brackets: false,
 3102    };
 3103
 3104    _ = editor.update(cx, |editor, window, cx| {
 3105        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3106            s.select_display_ranges([
 3107                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3108            ])
 3109        });
 3110        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3111        assert_eq!(
 3112            editor.buffer.read(cx).read(cx).text(),
 3113            "one\n   two\nthree\n   four"
 3114        );
 3115        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3116        assert_eq!(
 3117            editor.buffer.read(cx).read(cx).text(),
 3118            "\n   two\nthree\n   four"
 3119        );
 3120        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3121        assert_eq!(
 3122            editor.buffer.read(cx).read(cx).text(),
 3123            "two\nthree\n   four"
 3124        );
 3125        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3126        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3127        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3128        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3129        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3130        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3131        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3132        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3133    });
 3134}
 3135
 3136#[gpui::test]
 3137fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3138    init_test(cx, |_| {});
 3139
 3140    let editor = cx.add_window(|window, cx| {
 3141        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3142        build_editor(buffer, window, cx)
 3143    });
 3144    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3145        ignore_newlines: false,
 3146        ignore_brackets: false,
 3147    };
 3148    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3149        ignore_newlines: true,
 3150        ignore_brackets: false,
 3151    };
 3152
 3153    _ = editor.update(cx, |editor, window, cx| {
 3154        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3155            s.select_display_ranges([
 3156                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3157            ])
 3158        });
 3159        // Delete "\n" (empty line)
 3160        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3161        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3162        // Delete "foo" (subword boundary)
 3163        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3164        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3165        // Delete "Bar"
 3166        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3167        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3168        // Delete "\n   " (newline + leading whitespace)
 3169        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3170        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3171        // Delete "baz" (subword boundary)
 3172        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3173        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3174        // With ignore_newlines, delete "Qux"
 3175        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3176        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3177    });
 3178}
 3179
 3180#[gpui::test]
 3181fn test_newline(cx: &mut TestAppContext) {
 3182    init_test(cx, |_| {});
 3183
 3184    let editor = cx.add_window(|window, cx| {
 3185        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3186        build_editor(buffer, window, cx)
 3187    });
 3188
 3189    _ = editor.update(cx, |editor, window, cx| {
 3190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3191            s.select_display_ranges([
 3192                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3193                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3194                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3195            ])
 3196        });
 3197
 3198        editor.newline(&Newline, window, cx);
 3199        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3200    });
 3201}
 3202
 3203#[gpui::test]
 3204async fn test_newline_yaml(cx: &mut TestAppContext) {
 3205    init_test(cx, |_| {});
 3206
 3207    let mut cx = EditorTestContext::new(cx).await;
 3208    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3209    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3210
 3211    // Object (between 2 fields)
 3212    cx.set_state(indoc! {"
 3213    test:ˇ
 3214    hello: bye"});
 3215    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3216    cx.assert_editor_state(indoc! {"
 3217    test:
 3218        ˇ
 3219    hello: bye"});
 3220
 3221    // Object (first and single line)
 3222    cx.set_state(indoc! {"
 3223    test:ˇ"});
 3224    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3225    cx.assert_editor_state(indoc! {"
 3226    test:
 3227        ˇ"});
 3228
 3229    // Array with objects (after first element)
 3230    cx.set_state(indoc! {"
 3231    test:
 3232        - foo: barˇ"});
 3233    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3234    cx.assert_editor_state(indoc! {"
 3235    test:
 3236        - foo: bar
 3237        ˇ"});
 3238
 3239    // Array with objects and comment
 3240    cx.set_state(indoc! {"
 3241    test:
 3242        - foo: bar
 3243        - bar: # testˇ"});
 3244    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3245    cx.assert_editor_state(indoc! {"
 3246    test:
 3247        - foo: bar
 3248        - bar: # test
 3249            ˇ"});
 3250
 3251    // Array with objects (after second element)
 3252    cx.set_state(indoc! {"
 3253    test:
 3254        - foo: bar
 3255        - bar: fooˇ"});
 3256    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3257    cx.assert_editor_state(indoc! {"
 3258    test:
 3259        - foo: bar
 3260        - bar: foo
 3261        ˇ"});
 3262
 3263    // Array with strings (after first element)
 3264    cx.set_state(indoc! {"
 3265    test:
 3266        - fooˇ"});
 3267    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3268    cx.assert_editor_state(indoc! {"
 3269    test:
 3270        - foo
 3271        ˇ"});
 3272}
 3273
 3274#[gpui::test]
 3275fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3276    init_test(cx, |_| {});
 3277
 3278    let editor = cx.add_window(|window, cx| {
 3279        let buffer = MultiBuffer::build_simple(
 3280            "
 3281                a
 3282                b(
 3283                    X
 3284                )
 3285                c(
 3286                    X
 3287                )
 3288            "
 3289            .unindent()
 3290            .as_str(),
 3291            cx,
 3292        );
 3293        let mut editor = build_editor(buffer, window, cx);
 3294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3295            s.select_ranges([
 3296                Point::new(2, 4)..Point::new(2, 5),
 3297                Point::new(5, 4)..Point::new(5, 5),
 3298            ])
 3299        });
 3300        editor
 3301    });
 3302
 3303    _ = editor.update(cx, |editor, window, cx| {
 3304        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3305        editor.buffer.update(cx, |buffer, cx| {
 3306            buffer.edit(
 3307                [
 3308                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3309                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3310                ],
 3311                None,
 3312                cx,
 3313            );
 3314            assert_eq!(
 3315                buffer.read(cx).text(),
 3316                "
 3317                    a
 3318                    b()
 3319                    c()
 3320                "
 3321                .unindent()
 3322            );
 3323        });
 3324        assert_eq!(
 3325            editor.selections.ranges(&editor.display_snapshot(cx)),
 3326            &[
 3327                Point::new(1, 2)..Point::new(1, 2),
 3328                Point::new(2, 2)..Point::new(2, 2),
 3329            ],
 3330        );
 3331
 3332        editor.newline(&Newline, window, cx);
 3333        assert_eq!(
 3334            editor.text(cx),
 3335            "
 3336                a
 3337                b(
 3338                )
 3339                c(
 3340                )
 3341            "
 3342            .unindent()
 3343        );
 3344
 3345        // The selections are moved after the inserted newlines
 3346        assert_eq!(
 3347            editor.selections.ranges(&editor.display_snapshot(cx)),
 3348            &[
 3349                Point::new(2, 0)..Point::new(2, 0),
 3350                Point::new(4, 0)..Point::new(4, 0),
 3351            ],
 3352        );
 3353    });
 3354}
 3355
 3356#[gpui::test]
 3357async fn test_newline_above(cx: &mut TestAppContext) {
 3358    init_test(cx, |settings| {
 3359        settings.defaults.tab_size = NonZeroU32::new(4)
 3360    });
 3361
 3362    let language = Arc::new(
 3363        Language::new(
 3364            LanguageConfig::default(),
 3365            Some(tree_sitter_rust::LANGUAGE.into()),
 3366        )
 3367        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3368        .unwrap(),
 3369    );
 3370
 3371    let mut cx = EditorTestContext::new(cx).await;
 3372    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3373    cx.set_state(indoc! {"
 3374        const a: ˇA = (
 3375 3376                «const_functionˇ»(ˇ),
 3377                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3378 3379        ˇ);ˇ
 3380    "});
 3381
 3382    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3383    cx.assert_editor_state(indoc! {"
 3384        ˇ
 3385        const a: A = (
 3386            ˇ
 3387            (
 3388                ˇ
 3389                ˇ
 3390                const_function(),
 3391                ˇ
 3392                ˇ
 3393                ˇ
 3394                ˇ
 3395                something_else,
 3396                ˇ
 3397            )
 3398            ˇ
 3399            ˇ
 3400        );
 3401    "});
 3402}
 3403
 3404#[gpui::test]
 3405async fn test_newline_below(cx: &mut TestAppContext) {
 3406    init_test(cx, |settings| {
 3407        settings.defaults.tab_size = NonZeroU32::new(4)
 3408    });
 3409
 3410    let language = Arc::new(
 3411        Language::new(
 3412            LanguageConfig::default(),
 3413            Some(tree_sitter_rust::LANGUAGE.into()),
 3414        )
 3415        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3416        .unwrap(),
 3417    );
 3418
 3419    let mut cx = EditorTestContext::new(cx).await;
 3420    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3421    cx.set_state(indoc! {"
 3422        const a: ˇA = (
 3423 3424                «const_functionˇ»(ˇ),
 3425                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3426 3427        ˇ);ˇ
 3428    "});
 3429
 3430    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3431    cx.assert_editor_state(indoc! {"
 3432        const a: A = (
 3433            ˇ
 3434            (
 3435                ˇ
 3436                const_function(),
 3437                ˇ
 3438                ˇ
 3439                something_else,
 3440                ˇ
 3441                ˇ
 3442                ˇ
 3443                ˇ
 3444            )
 3445            ˇ
 3446        );
 3447        ˇ
 3448        ˇ
 3449    "});
 3450}
 3451
 3452#[gpui::test]
 3453async fn test_newline_comments(cx: &mut TestAppContext) {
 3454    init_test(cx, |settings| {
 3455        settings.defaults.tab_size = NonZeroU32::new(4)
 3456    });
 3457
 3458    let language = Arc::new(Language::new(
 3459        LanguageConfig {
 3460            line_comments: vec!["// ".into()],
 3461            ..LanguageConfig::default()
 3462        },
 3463        None,
 3464    ));
 3465    {
 3466        let mut cx = EditorTestContext::new(cx).await;
 3467        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3468        cx.set_state(indoc! {"
 3469        // Fooˇ
 3470    "});
 3471
 3472        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3473        cx.assert_editor_state(indoc! {"
 3474        // Foo
 3475        // ˇ
 3476    "});
 3477        // Ensure that we add comment prefix when existing line contains space
 3478        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3479        cx.assert_editor_state(
 3480            indoc! {"
 3481        // Foo
 3482        //s
 3483        // ˇ
 3484    "}
 3485            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3486            .as_str(),
 3487        );
 3488        // Ensure that we add comment prefix when existing line does not contain space
 3489        cx.set_state(indoc! {"
 3490        // Foo
 3491        //ˇ
 3492    "});
 3493        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3494        cx.assert_editor_state(indoc! {"
 3495        // Foo
 3496        //
 3497        // ˇ
 3498    "});
 3499        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3500        cx.set_state(indoc! {"
 3501        ˇ// Foo
 3502    "});
 3503        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3504        cx.assert_editor_state(indoc! {"
 3505
 3506        ˇ// Foo
 3507    "});
 3508    }
 3509    // Ensure that comment continuations can be disabled.
 3510    update_test_language_settings(cx, |settings| {
 3511        settings.defaults.extend_comment_on_newline = Some(false);
 3512    });
 3513    let mut cx = EditorTestContext::new(cx).await;
 3514    cx.set_state(indoc! {"
 3515        // Fooˇ
 3516    "});
 3517    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3518    cx.assert_editor_state(indoc! {"
 3519        // Foo
 3520        ˇ
 3521    "});
 3522}
 3523
 3524#[gpui::test]
 3525async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3526    init_test(cx, |settings| {
 3527        settings.defaults.tab_size = NonZeroU32::new(4)
 3528    });
 3529
 3530    let language = Arc::new(Language::new(
 3531        LanguageConfig {
 3532            line_comments: vec!["// ".into(), "/// ".into()],
 3533            ..LanguageConfig::default()
 3534        },
 3535        None,
 3536    ));
 3537    {
 3538        let mut cx = EditorTestContext::new(cx).await;
 3539        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3540        cx.set_state(indoc! {"
 3541        //ˇ
 3542    "});
 3543        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3544        cx.assert_editor_state(indoc! {"
 3545        //
 3546        // ˇ
 3547    "});
 3548
 3549        cx.set_state(indoc! {"
 3550        ///ˇ
 3551    "});
 3552        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3553        cx.assert_editor_state(indoc! {"
 3554        ///
 3555        /// ˇ
 3556    "});
 3557    }
 3558}
 3559
 3560#[gpui::test]
 3561async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3562    init_test(cx, |settings| {
 3563        settings.defaults.tab_size = NonZeroU32::new(4)
 3564    });
 3565
 3566    let language = Arc::new(
 3567        Language::new(
 3568            LanguageConfig {
 3569                documentation_comment: Some(language::BlockCommentConfig {
 3570                    start: "/**".into(),
 3571                    end: "*/".into(),
 3572                    prefix: "* ".into(),
 3573                    tab_size: 1,
 3574                }),
 3575
 3576                ..LanguageConfig::default()
 3577            },
 3578            Some(tree_sitter_rust::LANGUAGE.into()),
 3579        )
 3580        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3581        .unwrap(),
 3582    );
 3583
 3584    {
 3585        let mut cx = EditorTestContext::new(cx).await;
 3586        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3587        cx.set_state(indoc! {"
 3588        /**ˇ
 3589    "});
 3590
 3591        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3592        cx.assert_editor_state(indoc! {"
 3593        /**
 3594         * ˇ
 3595    "});
 3596        // Ensure that if cursor is before the comment start,
 3597        // we do not actually insert a comment prefix.
 3598        cx.set_state(indoc! {"
 3599        ˇ/**
 3600    "});
 3601        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3602        cx.assert_editor_state(indoc! {"
 3603
 3604        ˇ/**
 3605    "});
 3606        // Ensure that if cursor is between it doesn't add comment prefix.
 3607        cx.set_state(indoc! {"
 3608        /*ˇ*
 3609    "});
 3610        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3611        cx.assert_editor_state(indoc! {"
 3612        /*
 3613        ˇ*
 3614    "});
 3615        // Ensure that if suffix exists on same line after cursor it adds new line.
 3616        cx.set_state(indoc! {"
 3617        /**ˇ*/
 3618    "});
 3619        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3620        cx.assert_editor_state(indoc! {"
 3621        /**
 3622         * ˇ
 3623         */
 3624    "});
 3625        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3626        cx.set_state(indoc! {"
 3627        /**ˇ */
 3628    "});
 3629        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3630        cx.assert_editor_state(indoc! {"
 3631        /**
 3632         * ˇ
 3633         */
 3634    "});
 3635        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3636        cx.set_state(indoc! {"
 3637        /** ˇ*/
 3638    "});
 3639        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3640        cx.assert_editor_state(
 3641            indoc! {"
 3642        /**s
 3643         * ˇ
 3644         */
 3645    "}
 3646            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3647            .as_str(),
 3648        );
 3649        // Ensure that delimiter space is preserved when newline on already
 3650        // spaced delimiter.
 3651        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3652        cx.assert_editor_state(
 3653            indoc! {"
 3654        /**s
 3655         *s
 3656         * ˇ
 3657         */
 3658    "}
 3659            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3660            .as_str(),
 3661        );
 3662        // Ensure that delimiter space is preserved when space is not
 3663        // on existing delimiter.
 3664        cx.set_state(indoc! {"
 3665        /**
 3666 3667         */
 3668    "});
 3669        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3670        cx.assert_editor_state(indoc! {"
 3671        /**
 3672         *
 3673         * ˇ
 3674         */
 3675    "});
 3676        // Ensure that if suffix exists on same line after cursor it
 3677        // doesn't add extra new line if prefix is not on same line.
 3678        cx.set_state(indoc! {"
 3679        /**
 3680        ˇ*/
 3681    "});
 3682        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3683        cx.assert_editor_state(indoc! {"
 3684        /**
 3685
 3686        ˇ*/
 3687    "});
 3688        // Ensure that it detects suffix after existing prefix.
 3689        cx.set_state(indoc! {"
 3690        /**ˇ/
 3691    "});
 3692        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3693        cx.assert_editor_state(indoc! {"
 3694        /**
 3695        ˇ/
 3696    "});
 3697        // Ensure that if suffix exists on same line before
 3698        // cursor it does not add comment prefix.
 3699        cx.set_state(indoc! {"
 3700        /** */ˇ
 3701    "});
 3702        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3703        cx.assert_editor_state(indoc! {"
 3704        /** */
 3705        ˇ
 3706    "});
 3707        // Ensure that if suffix exists on same line before
 3708        // cursor it does not add comment prefix.
 3709        cx.set_state(indoc! {"
 3710        /**
 3711         *
 3712         */ˇ
 3713    "});
 3714        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3715        cx.assert_editor_state(indoc! {"
 3716        /**
 3717         *
 3718         */
 3719         ˇ
 3720    "});
 3721
 3722        // Ensure that inline comment followed by code
 3723        // doesn't add comment prefix on newline
 3724        cx.set_state(indoc! {"
 3725        /** */ textˇ
 3726    "});
 3727        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3728        cx.assert_editor_state(indoc! {"
 3729        /** */ text
 3730        ˇ
 3731    "});
 3732
 3733        // Ensure that text after comment end tag
 3734        // doesn't add comment prefix on newline
 3735        cx.set_state(indoc! {"
 3736        /**
 3737         *
 3738         */ˇtext
 3739    "});
 3740        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3741        cx.assert_editor_state(indoc! {"
 3742        /**
 3743         *
 3744         */
 3745         ˇtext
 3746    "});
 3747
 3748        // Ensure if not comment block it doesn't
 3749        // add comment prefix on newline
 3750        cx.set_state(indoc! {"
 3751        * textˇ
 3752    "});
 3753        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3754        cx.assert_editor_state(indoc! {"
 3755        * text
 3756        ˇ
 3757    "});
 3758    }
 3759    // Ensure that comment continuations can be disabled.
 3760    update_test_language_settings(cx, |settings| {
 3761        settings.defaults.extend_comment_on_newline = Some(false);
 3762    });
 3763    let mut cx = EditorTestContext::new(cx).await;
 3764    cx.set_state(indoc! {"
 3765        /**ˇ
 3766    "});
 3767    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3768    cx.assert_editor_state(indoc! {"
 3769        /**
 3770        ˇ
 3771    "});
 3772}
 3773
 3774#[gpui::test]
 3775async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3776    init_test(cx, |settings| {
 3777        settings.defaults.tab_size = NonZeroU32::new(4)
 3778    });
 3779
 3780    let lua_language = Arc::new(Language::new(
 3781        LanguageConfig {
 3782            line_comments: vec!["--".into()],
 3783            block_comment: Some(language::BlockCommentConfig {
 3784                start: "--[[".into(),
 3785                prefix: "".into(),
 3786                end: "]]".into(),
 3787                tab_size: 0,
 3788            }),
 3789            ..LanguageConfig::default()
 3790        },
 3791        None,
 3792    ));
 3793
 3794    let mut cx = EditorTestContext::new(cx).await;
 3795    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3796
 3797    // Line with line comment should extend
 3798    cx.set_state(indoc! {"
 3799        --ˇ
 3800    "});
 3801    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3802    cx.assert_editor_state(indoc! {"
 3803        --
 3804        --ˇ
 3805    "});
 3806
 3807    // Line with block comment that matches line comment should not extend
 3808    cx.set_state(indoc! {"
 3809        --[[ˇ
 3810    "});
 3811    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3812    cx.assert_editor_state(indoc! {"
 3813        --[[
 3814        ˇ
 3815    "});
 3816}
 3817
 3818#[gpui::test]
 3819fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3820    init_test(cx, |_| {});
 3821
 3822    let editor = cx.add_window(|window, cx| {
 3823        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3824        let mut editor = build_editor(buffer, window, cx);
 3825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3826            s.select_ranges([
 3827                MultiBufferOffset(3)..MultiBufferOffset(4),
 3828                MultiBufferOffset(11)..MultiBufferOffset(12),
 3829                MultiBufferOffset(19)..MultiBufferOffset(20),
 3830            ])
 3831        });
 3832        editor
 3833    });
 3834
 3835    _ = editor.update(cx, |editor, window, cx| {
 3836        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3837        editor.buffer.update(cx, |buffer, cx| {
 3838            buffer.edit(
 3839                [
 3840                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3841                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3842                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3843                ],
 3844                None,
 3845                cx,
 3846            );
 3847            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3848        });
 3849        assert_eq!(
 3850            editor.selections.ranges(&editor.display_snapshot(cx)),
 3851            &[
 3852                MultiBufferOffset(2)..MultiBufferOffset(2),
 3853                MultiBufferOffset(7)..MultiBufferOffset(7),
 3854                MultiBufferOffset(12)..MultiBufferOffset(12)
 3855            ],
 3856        );
 3857
 3858        editor.insert("Z", window, cx);
 3859        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3860
 3861        // The selections are moved after the inserted characters
 3862        assert_eq!(
 3863            editor.selections.ranges(&editor.display_snapshot(cx)),
 3864            &[
 3865                MultiBufferOffset(3)..MultiBufferOffset(3),
 3866                MultiBufferOffset(9)..MultiBufferOffset(9),
 3867                MultiBufferOffset(15)..MultiBufferOffset(15)
 3868            ],
 3869        );
 3870    });
 3871}
 3872
 3873#[gpui::test]
 3874async fn test_tab(cx: &mut TestAppContext) {
 3875    init_test(cx, |settings| {
 3876        settings.defaults.tab_size = NonZeroU32::new(3)
 3877    });
 3878
 3879    let mut cx = EditorTestContext::new(cx).await;
 3880    cx.set_state(indoc! {"
 3881        ˇabˇc
 3882        ˇ🏀ˇ🏀ˇefg
 3883 3884    "});
 3885    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3886    cx.assert_editor_state(indoc! {"
 3887           ˇab ˇc
 3888           ˇ🏀  ˇ🏀  ˇefg
 3889        d  ˇ
 3890    "});
 3891
 3892    cx.set_state(indoc! {"
 3893        a
 3894        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3895    "});
 3896    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3897    cx.assert_editor_state(indoc! {"
 3898        a
 3899           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3900    "});
 3901}
 3902
 3903#[gpui::test]
 3904async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3905    init_test(cx, |_| {});
 3906
 3907    let mut cx = EditorTestContext::new(cx).await;
 3908    let language = Arc::new(
 3909        Language::new(
 3910            LanguageConfig::default(),
 3911            Some(tree_sitter_rust::LANGUAGE.into()),
 3912        )
 3913        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3914        .unwrap(),
 3915    );
 3916    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3917
 3918    // test when all cursors are not at suggested indent
 3919    // then simply move to their suggested indent location
 3920    cx.set_state(indoc! {"
 3921        const a: B = (
 3922            c(
 3923        ˇ
 3924        ˇ    )
 3925        );
 3926    "});
 3927    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3928    cx.assert_editor_state(indoc! {"
 3929        const a: B = (
 3930            c(
 3931                ˇ
 3932            ˇ)
 3933        );
 3934    "});
 3935
 3936    // test cursor already at suggested indent not moving when
 3937    // other cursors are yet to reach their suggested indents
 3938    cx.set_state(indoc! {"
 3939        ˇ
 3940        const a: B = (
 3941            c(
 3942                d(
 3943        ˇ
 3944                )
 3945        ˇ
 3946        ˇ    )
 3947        );
 3948    "});
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950    cx.assert_editor_state(indoc! {"
 3951        ˇ
 3952        const a: B = (
 3953            c(
 3954                d(
 3955                    ˇ
 3956                )
 3957                ˇ
 3958            ˇ)
 3959        );
 3960    "});
 3961    // test when all cursors are at suggested indent then tab is inserted
 3962    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964            ˇ
 3965        const a: B = (
 3966            c(
 3967                d(
 3968                        ˇ
 3969                )
 3970                    ˇ
 3971                ˇ)
 3972        );
 3973    "});
 3974
 3975    // test when current indent is less than suggested indent,
 3976    // we adjust line to match suggested indent and move cursor to it
 3977    //
 3978    // when no other cursor is at word boundary, all of them should move
 3979    cx.set_state(indoc! {"
 3980        const a: B = (
 3981            c(
 3982                d(
 3983        ˇ
 3984        ˇ   )
 3985        ˇ   )
 3986        );
 3987    "});
 3988    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3989    cx.assert_editor_state(indoc! {"
 3990        const a: B = (
 3991            c(
 3992                d(
 3993                    ˇ
 3994                ˇ)
 3995            ˇ)
 3996        );
 3997    "});
 3998
 3999    // test when current indent is less than suggested indent,
 4000    // we adjust line to match suggested indent and move cursor to it
 4001    //
 4002    // when some other cursor is at word boundary, it should not move
 4003    cx.set_state(indoc! {"
 4004        const a: B = (
 4005            c(
 4006                d(
 4007        ˇ
 4008        ˇ   )
 4009           ˇ)
 4010        );
 4011    "});
 4012    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4013    cx.assert_editor_state(indoc! {"
 4014        const a: B = (
 4015            c(
 4016                d(
 4017                    ˇ
 4018                ˇ)
 4019            ˇ)
 4020        );
 4021    "});
 4022
 4023    // test when current indent is more than suggested indent,
 4024    // we just move cursor to current indent instead of suggested indent
 4025    //
 4026    // when no other cursor is at word boundary, all of them should move
 4027    cx.set_state(indoc! {"
 4028        const a: B = (
 4029            c(
 4030                d(
 4031        ˇ
 4032        ˇ                )
 4033        ˇ   )
 4034        );
 4035    "});
 4036    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4037    cx.assert_editor_state(indoc! {"
 4038        const a: B = (
 4039            c(
 4040                d(
 4041                    ˇ
 4042                        ˇ)
 4043            ˇ)
 4044        );
 4045    "});
 4046    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4047    cx.assert_editor_state(indoc! {"
 4048        const a: B = (
 4049            c(
 4050                d(
 4051                        ˇ
 4052                            ˇ)
 4053                ˇ)
 4054        );
 4055    "});
 4056
 4057    // test when current indent is more than suggested indent,
 4058    // we just move cursor to current indent instead of suggested indent
 4059    //
 4060    // when some other cursor is at word boundary, it doesn't move
 4061    cx.set_state(indoc! {"
 4062        const a: B = (
 4063            c(
 4064                d(
 4065        ˇ
 4066        ˇ                )
 4067            ˇ)
 4068        );
 4069    "});
 4070    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4071    cx.assert_editor_state(indoc! {"
 4072        const a: B = (
 4073            c(
 4074                d(
 4075                    ˇ
 4076                        ˇ)
 4077            ˇ)
 4078        );
 4079    "});
 4080
 4081    // handle auto-indent when there are multiple cursors on the same line
 4082    cx.set_state(indoc! {"
 4083        const a: B = (
 4084            c(
 4085        ˇ    ˇ
 4086        ˇ    )
 4087        );
 4088    "});
 4089    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4090    cx.assert_editor_state(indoc! {"
 4091        const a: B = (
 4092            c(
 4093                ˇ
 4094            ˇ)
 4095        );
 4096    "});
 4097}
 4098
 4099#[gpui::test]
 4100async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4101    init_test(cx, |settings| {
 4102        settings.defaults.tab_size = NonZeroU32::new(3)
 4103    });
 4104
 4105    let mut cx = EditorTestContext::new(cx).await;
 4106    cx.set_state(indoc! {"
 4107         ˇ
 4108        \t ˇ
 4109        \t  ˇ
 4110        \t   ˇ
 4111         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4112    "});
 4113
 4114    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4115    cx.assert_editor_state(indoc! {"
 4116           ˇ
 4117        \t   ˇ
 4118        \t   ˇ
 4119        \t      ˇ
 4120         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4121    "});
 4122}
 4123
 4124#[gpui::test]
 4125async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4126    init_test(cx, |settings| {
 4127        settings.defaults.tab_size = NonZeroU32::new(4)
 4128    });
 4129
 4130    let language = Arc::new(
 4131        Language::new(
 4132            LanguageConfig::default(),
 4133            Some(tree_sitter_rust::LANGUAGE.into()),
 4134        )
 4135        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4136        .unwrap(),
 4137    );
 4138
 4139    let mut cx = EditorTestContext::new(cx).await;
 4140    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4141    cx.set_state(indoc! {"
 4142        fn a() {
 4143            if b {
 4144        \t ˇc
 4145            }
 4146        }
 4147    "});
 4148
 4149    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4150    cx.assert_editor_state(indoc! {"
 4151        fn a() {
 4152            if b {
 4153                ˇc
 4154            }
 4155        }
 4156    "});
 4157}
 4158
 4159#[gpui::test]
 4160async fn test_indent_outdent(cx: &mut TestAppContext) {
 4161    init_test(cx, |settings| {
 4162        settings.defaults.tab_size = NonZeroU32::new(4);
 4163    });
 4164
 4165    let mut cx = EditorTestContext::new(cx).await;
 4166
 4167    cx.set_state(indoc! {"
 4168          «oneˇ» «twoˇ»
 4169        three
 4170         four
 4171    "});
 4172    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4173    cx.assert_editor_state(indoc! {"
 4174            «oneˇ» «twoˇ»
 4175        three
 4176         four
 4177    "});
 4178
 4179    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4180    cx.assert_editor_state(indoc! {"
 4181        «oneˇ» «twoˇ»
 4182        three
 4183         four
 4184    "});
 4185
 4186    // select across line ending
 4187    cx.set_state(indoc! {"
 4188        one two
 4189        t«hree
 4190        ˇ» four
 4191    "});
 4192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4193    cx.assert_editor_state(indoc! {"
 4194        one two
 4195            t«hree
 4196        ˇ» four
 4197    "});
 4198
 4199    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4200    cx.assert_editor_state(indoc! {"
 4201        one two
 4202        t«hree
 4203        ˇ» four
 4204    "});
 4205
 4206    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4207    cx.set_state(indoc! {"
 4208        one two
 4209        ˇthree
 4210            four
 4211    "});
 4212    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4213    cx.assert_editor_state(indoc! {"
 4214        one two
 4215            ˇthree
 4216            four
 4217    "});
 4218
 4219    cx.set_state(indoc! {"
 4220        one two
 4221        ˇ    three
 4222            four
 4223    "});
 4224    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4225    cx.assert_editor_state(indoc! {"
 4226        one two
 4227        ˇthree
 4228            four
 4229    "});
 4230}
 4231
 4232#[gpui::test]
 4233async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4234    // This is a regression test for issue #33761
 4235    init_test(cx, |_| {});
 4236
 4237    let mut cx = EditorTestContext::new(cx).await;
 4238    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4239    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4240
 4241    cx.set_state(
 4242        r#"ˇ#     ingress:
 4243ˇ#         api:
 4244ˇ#             enabled: false
 4245ˇ#             pathType: Prefix
 4246ˇ#           console:
 4247ˇ#               enabled: false
 4248ˇ#               pathType: Prefix
 4249"#,
 4250    );
 4251
 4252    // Press tab to indent all lines
 4253    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4254
 4255    cx.assert_editor_state(
 4256        r#"    ˇ#     ingress:
 4257    ˇ#         api:
 4258    ˇ#             enabled: false
 4259    ˇ#             pathType: Prefix
 4260    ˇ#           console:
 4261    ˇ#               enabled: false
 4262    ˇ#               pathType: Prefix
 4263"#,
 4264    );
 4265}
 4266
 4267#[gpui::test]
 4268async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4269    // This is a test to make sure our fix for issue #33761 didn't break anything
 4270    init_test(cx, |_| {});
 4271
 4272    let mut cx = EditorTestContext::new(cx).await;
 4273    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4274    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4275
 4276    cx.set_state(
 4277        r#"ˇingress:
 4278ˇ  api:
 4279ˇ    enabled: false
 4280ˇ    pathType: Prefix
 4281"#,
 4282    );
 4283
 4284    // Press tab to indent all lines
 4285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4286
 4287    cx.assert_editor_state(
 4288        r#"ˇingress:
 4289    ˇapi:
 4290        ˇenabled: false
 4291        ˇpathType: Prefix
 4292"#,
 4293    );
 4294}
 4295
 4296#[gpui::test]
 4297async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4298    init_test(cx, |settings| {
 4299        settings.defaults.hard_tabs = Some(true);
 4300    });
 4301
 4302    let mut cx = EditorTestContext::new(cx).await;
 4303
 4304    // select two ranges on one line
 4305    cx.set_state(indoc! {"
 4306        «oneˇ» «twoˇ»
 4307        three
 4308        four
 4309    "});
 4310    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4311    cx.assert_editor_state(indoc! {"
 4312        \t«oneˇ» «twoˇ»
 4313        three
 4314        four
 4315    "});
 4316    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4317    cx.assert_editor_state(indoc! {"
 4318        \t\t«oneˇ» «twoˇ»
 4319        three
 4320        four
 4321    "});
 4322    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4323    cx.assert_editor_state(indoc! {"
 4324        \t«oneˇ» «twoˇ»
 4325        three
 4326        four
 4327    "});
 4328    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4329    cx.assert_editor_state(indoc! {"
 4330        «oneˇ» «twoˇ»
 4331        three
 4332        four
 4333    "});
 4334
 4335    // select across a line ending
 4336    cx.set_state(indoc! {"
 4337        one two
 4338        t«hree
 4339        ˇ»four
 4340    "});
 4341    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4342    cx.assert_editor_state(indoc! {"
 4343        one two
 4344        \tt«hree
 4345        ˇ»four
 4346    "});
 4347    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4348    cx.assert_editor_state(indoc! {"
 4349        one two
 4350        \t\tt«hree
 4351        ˇ»four
 4352    "});
 4353    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4354    cx.assert_editor_state(indoc! {"
 4355        one two
 4356        \tt«hree
 4357        ˇ»four
 4358    "});
 4359    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4360    cx.assert_editor_state(indoc! {"
 4361        one two
 4362        t«hree
 4363        ˇ»four
 4364    "});
 4365
 4366    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4367    cx.set_state(indoc! {"
 4368        one two
 4369        ˇthree
 4370        four
 4371    "});
 4372    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4373    cx.assert_editor_state(indoc! {"
 4374        one two
 4375        ˇthree
 4376        four
 4377    "});
 4378    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4379    cx.assert_editor_state(indoc! {"
 4380        one two
 4381        \tˇthree
 4382        four
 4383    "});
 4384    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4385    cx.assert_editor_state(indoc! {"
 4386        one two
 4387        ˇthree
 4388        four
 4389    "});
 4390}
 4391
 4392#[gpui::test]
 4393fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4394    init_test(cx, |settings| {
 4395        settings.languages.0.extend([
 4396            (
 4397                "TOML".into(),
 4398                LanguageSettingsContent {
 4399                    tab_size: NonZeroU32::new(2),
 4400                    ..Default::default()
 4401                },
 4402            ),
 4403            (
 4404                "Rust".into(),
 4405                LanguageSettingsContent {
 4406                    tab_size: NonZeroU32::new(4),
 4407                    ..Default::default()
 4408                },
 4409            ),
 4410        ]);
 4411    });
 4412
 4413    let toml_language = Arc::new(Language::new(
 4414        LanguageConfig {
 4415            name: "TOML".into(),
 4416            ..Default::default()
 4417        },
 4418        None,
 4419    ));
 4420    let rust_language = Arc::new(Language::new(
 4421        LanguageConfig {
 4422            name: "Rust".into(),
 4423            ..Default::default()
 4424        },
 4425        None,
 4426    ));
 4427
 4428    let toml_buffer =
 4429        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4430    let rust_buffer =
 4431        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4432    let multibuffer = cx.new(|cx| {
 4433        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4434        multibuffer.push_excerpts(
 4435            toml_buffer.clone(),
 4436            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4437            cx,
 4438        );
 4439        multibuffer.push_excerpts(
 4440            rust_buffer.clone(),
 4441            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4442            cx,
 4443        );
 4444        multibuffer
 4445    });
 4446
 4447    cx.add_window(|window, cx| {
 4448        let mut editor = build_editor(multibuffer, window, cx);
 4449
 4450        assert_eq!(
 4451            editor.text(cx),
 4452            indoc! {"
 4453                a = 1
 4454                b = 2
 4455
 4456                const c: usize = 3;
 4457            "}
 4458        );
 4459
 4460        select_ranges(
 4461            &mut editor,
 4462            indoc! {"
 4463                «aˇ» = 1
 4464                b = 2
 4465
 4466                «const c:ˇ» usize = 3;
 4467            "},
 4468            window,
 4469            cx,
 4470        );
 4471
 4472        editor.tab(&Tab, window, cx);
 4473        assert_text_with_selections(
 4474            &mut editor,
 4475            indoc! {"
 4476                  «aˇ» = 1
 4477                b = 2
 4478
 4479                    «const c:ˇ» usize = 3;
 4480            "},
 4481            cx,
 4482        );
 4483        editor.backtab(&Backtab, window, cx);
 4484        assert_text_with_selections(
 4485            &mut editor,
 4486            indoc! {"
 4487                «aˇ» = 1
 4488                b = 2
 4489
 4490                «const c:ˇ» usize = 3;
 4491            "},
 4492            cx,
 4493        );
 4494
 4495        editor
 4496    });
 4497}
 4498
 4499#[gpui::test]
 4500async fn test_backspace(cx: &mut TestAppContext) {
 4501    init_test(cx, |_| {});
 4502
 4503    let mut cx = EditorTestContext::new(cx).await;
 4504
 4505    // Basic backspace
 4506    cx.set_state(indoc! {"
 4507        onˇe two three
 4508        fou«rˇ» five six
 4509        seven «ˇeight nine
 4510        »ten
 4511    "});
 4512    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4513    cx.assert_editor_state(indoc! {"
 4514        oˇe two three
 4515        fouˇ five six
 4516        seven ˇten
 4517    "});
 4518
 4519    // Test backspace inside and around indents
 4520    cx.set_state(indoc! {"
 4521        zero
 4522            ˇone
 4523                ˇtwo
 4524            ˇ ˇ ˇ  three
 4525        ˇ  ˇ  four
 4526    "});
 4527    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4528    cx.assert_editor_state(indoc! {"
 4529        zero
 4530        ˇone
 4531            ˇtwo
 4532        ˇ  threeˇ  four
 4533    "});
 4534}
 4535
 4536#[gpui::test]
 4537async fn test_delete(cx: &mut TestAppContext) {
 4538    init_test(cx, |_| {});
 4539
 4540    let mut cx = EditorTestContext::new(cx).await;
 4541    cx.set_state(indoc! {"
 4542        onˇe two three
 4543        fou«rˇ» five six
 4544        seven «ˇeight nine
 4545        »ten
 4546    "});
 4547    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4548    cx.assert_editor_state(indoc! {"
 4549        onˇ two three
 4550        fouˇ five six
 4551        seven ˇten
 4552    "});
 4553}
 4554
 4555#[gpui::test]
 4556fn test_delete_line(cx: &mut TestAppContext) {
 4557    init_test(cx, |_| {});
 4558
 4559    let editor = cx.add_window(|window, cx| {
 4560        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4561        build_editor(buffer, window, cx)
 4562    });
 4563    _ = editor.update(cx, |editor, window, cx| {
 4564        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4565            s.select_display_ranges([
 4566                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4567                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4568                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4569            ])
 4570        });
 4571        editor.delete_line(&DeleteLine, window, cx);
 4572        assert_eq!(editor.display_text(cx), "ghi");
 4573        assert_eq!(
 4574            display_ranges(editor, cx),
 4575            vec![
 4576                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4577                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4578            ]
 4579        );
 4580    });
 4581
 4582    let editor = cx.add_window(|window, cx| {
 4583        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4584        build_editor(buffer, window, cx)
 4585    });
 4586    _ = editor.update(cx, |editor, window, cx| {
 4587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4588            s.select_display_ranges([
 4589                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4590            ])
 4591        });
 4592        editor.delete_line(&DeleteLine, window, cx);
 4593        assert_eq!(editor.display_text(cx), "ghi\n");
 4594        assert_eq!(
 4595            display_ranges(editor, cx),
 4596            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4597        );
 4598    });
 4599
 4600    let editor = cx.add_window(|window, cx| {
 4601        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4602        build_editor(buffer, window, cx)
 4603    });
 4604    _ = editor.update(cx, |editor, window, cx| {
 4605        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4606            s.select_display_ranges([
 4607                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4608            ])
 4609        });
 4610        editor.delete_line(&DeleteLine, window, cx);
 4611        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4612        assert_eq!(
 4613            display_ranges(editor, cx),
 4614            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4615        );
 4616    });
 4617}
 4618
 4619#[gpui::test]
 4620fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4621    init_test(cx, |_| {});
 4622
 4623    cx.add_window(|window, cx| {
 4624        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4625        let mut editor = build_editor(buffer.clone(), window, cx);
 4626        let buffer = buffer.read(cx).as_singleton().unwrap();
 4627
 4628        assert_eq!(
 4629            editor
 4630                .selections
 4631                .ranges::<Point>(&editor.display_snapshot(cx)),
 4632            &[Point::new(0, 0)..Point::new(0, 0)]
 4633        );
 4634
 4635        // When on single line, replace newline at end by space
 4636        editor.join_lines(&JoinLines, window, cx);
 4637        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4638        assert_eq!(
 4639            editor
 4640                .selections
 4641                .ranges::<Point>(&editor.display_snapshot(cx)),
 4642            &[Point::new(0, 3)..Point::new(0, 3)]
 4643        );
 4644
 4645        // When multiple lines are selected, remove newlines that are spanned by the selection
 4646        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4647            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4648        });
 4649        editor.join_lines(&JoinLines, window, cx);
 4650        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4651        assert_eq!(
 4652            editor
 4653                .selections
 4654                .ranges::<Point>(&editor.display_snapshot(cx)),
 4655            &[Point::new(0, 11)..Point::new(0, 11)]
 4656        );
 4657
 4658        // Undo should be transactional
 4659        editor.undo(&Undo, window, cx);
 4660        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4661        assert_eq!(
 4662            editor
 4663                .selections
 4664                .ranges::<Point>(&editor.display_snapshot(cx)),
 4665            &[Point::new(0, 5)..Point::new(2, 2)]
 4666        );
 4667
 4668        // When joining an empty line don't insert a space
 4669        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4670            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4671        });
 4672        editor.join_lines(&JoinLines, window, cx);
 4673        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4674        assert_eq!(
 4675            editor
 4676                .selections
 4677                .ranges::<Point>(&editor.display_snapshot(cx)),
 4678            [Point::new(2, 3)..Point::new(2, 3)]
 4679        );
 4680
 4681        // We can remove trailing newlines
 4682        editor.join_lines(&JoinLines, window, cx);
 4683        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4684        assert_eq!(
 4685            editor
 4686                .selections
 4687                .ranges::<Point>(&editor.display_snapshot(cx)),
 4688            [Point::new(2, 3)..Point::new(2, 3)]
 4689        );
 4690
 4691        // We don't blow up on the last line
 4692        editor.join_lines(&JoinLines, window, cx);
 4693        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4694        assert_eq!(
 4695            editor
 4696                .selections
 4697                .ranges::<Point>(&editor.display_snapshot(cx)),
 4698            [Point::new(2, 3)..Point::new(2, 3)]
 4699        );
 4700
 4701        // reset to test indentation
 4702        editor.buffer.update(cx, |buffer, cx| {
 4703            buffer.edit(
 4704                [
 4705                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4706                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4707                ],
 4708                None,
 4709                cx,
 4710            )
 4711        });
 4712
 4713        // We remove any leading spaces
 4714        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4716            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4717        });
 4718        editor.join_lines(&JoinLines, window, cx);
 4719        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4720
 4721        // We don't insert a space for a line containing only spaces
 4722        editor.join_lines(&JoinLines, window, cx);
 4723        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4724
 4725        // We ignore any leading tabs
 4726        editor.join_lines(&JoinLines, window, cx);
 4727        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4728
 4729        editor
 4730    });
 4731}
 4732
 4733#[gpui::test]
 4734fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4735    init_test(cx, |_| {});
 4736
 4737    cx.add_window(|window, cx| {
 4738        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4739        let mut editor = build_editor(buffer.clone(), window, cx);
 4740        let buffer = buffer.read(cx).as_singleton().unwrap();
 4741
 4742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4743            s.select_ranges([
 4744                Point::new(0, 2)..Point::new(1, 1),
 4745                Point::new(1, 2)..Point::new(1, 2),
 4746                Point::new(3, 1)..Point::new(3, 2),
 4747            ])
 4748        });
 4749
 4750        editor.join_lines(&JoinLines, window, cx);
 4751        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4752
 4753        assert_eq!(
 4754            editor
 4755                .selections
 4756                .ranges::<Point>(&editor.display_snapshot(cx)),
 4757            [
 4758                Point::new(0, 7)..Point::new(0, 7),
 4759                Point::new(1, 3)..Point::new(1, 3)
 4760            ]
 4761        );
 4762        editor
 4763    });
 4764}
 4765
 4766#[gpui::test]
 4767async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4768    init_test(cx, |_| {});
 4769
 4770    let mut cx = EditorTestContext::new(cx).await;
 4771
 4772    let diff_base = r#"
 4773        Line 0
 4774        Line 1
 4775        Line 2
 4776        Line 3
 4777        "#
 4778    .unindent();
 4779
 4780    cx.set_state(
 4781        &r#"
 4782        ˇLine 0
 4783        Line 1
 4784        Line 2
 4785        Line 3
 4786        "#
 4787        .unindent(),
 4788    );
 4789
 4790    cx.set_head_text(&diff_base);
 4791    executor.run_until_parked();
 4792
 4793    // Join lines
 4794    cx.update_editor(|editor, window, cx| {
 4795        editor.join_lines(&JoinLines, window, cx);
 4796    });
 4797    executor.run_until_parked();
 4798
 4799    cx.assert_editor_state(
 4800        &r#"
 4801        Line 0ˇ Line 1
 4802        Line 2
 4803        Line 3
 4804        "#
 4805        .unindent(),
 4806    );
 4807    // Join again
 4808    cx.update_editor(|editor, window, cx| {
 4809        editor.join_lines(&JoinLines, window, cx);
 4810    });
 4811    executor.run_until_parked();
 4812
 4813    cx.assert_editor_state(
 4814        &r#"
 4815        Line 0 Line 1ˇ Line 2
 4816        Line 3
 4817        "#
 4818        .unindent(),
 4819    );
 4820}
 4821
 4822#[gpui::test]
 4823async fn test_custom_newlines_cause_no_false_positive_diffs(
 4824    executor: BackgroundExecutor,
 4825    cx: &mut TestAppContext,
 4826) {
 4827    init_test(cx, |_| {});
 4828    let mut cx = EditorTestContext::new(cx).await;
 4829    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4830    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4831    executor.run_until_parked();
 4832
 4833    cx.update_editor(|editor, window, cx| {
 4834        let snapshot = editor.snapshot(window, cx);
 4835        assert_eq!(
 4836            snapshot
 4837                .buffer_snapshot()
 4838                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4839                .collect::<Vec<_>>(),
 4840            Vec::new(),
 4841            "Should not have any diffs for files with custom newlines"
 4842        );
 4843    });
 4844}
 4845
 4846#[gpui::test]
 4847async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4848    init_test(cx, |_| {});
 4849
 4850    let mut cx = EditorTestContext::new(cx).await;
 4851
 4852    // Test sort_lines_case_insensitive()
 4853    cx.set_state(indoc! {"
 4854        «z
 4855        y
 4856        x
 4857        Z
 4858        Y
 4859        Xˇ»
 4860    "});
 4861    cx.update_editor(|e, window, cx| {
 4862        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4863    });
 4864    cx.assert_editor_state(indoc! {"
 4865        «x
 4866        X
 4867        y
 4868        Y
 4869        z
 4870        Zˇ»
 4871    "});
 4872
 4873    // Test sort_lines_by_length()
 4874    //
 4875    // Demonstrates:
 4876    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4877    // - sort is stable
 4878    cx.set_state(indoc! {"
 4879        «123
 4880        æ
 4881        12
 4882 4883        1
 4884        æˇ»
 4885    "});
 4886    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4887    cx.assert_editor_state(indoc! {"
 4888        «æ
 4889 4890        1
 4891        æ
 4892        12
 4893        123ˇ»
 4894    "});
 4895
 4896    // Test reverse_lines()
 4897    cx.set_state(indoc! {"
 4898        «5
 4899        4
 4900        3
 4901        2
 4902        1ˇ»
 4903    "});
 4904    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4905    cx.assert_editor_state(indoc! {"
 4906        «1
 4907        2
 4908        3
 4909        4
 4910        5ˇ»
 4911    "});
 4912
 4913    // Skip testing shuffle_line()
 4914
 4915    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4916    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4917
 4918    // Don't manipulate when cursor is on single line, but expand the selection
 4919    cx.set_state(indoc! {"
 4920        ddˇdd
 4921        ccc
 4922        bb
 4923        a
 4924    "});
 4925    cx.update_editor(|e, window, cx| {
 4926        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4927    });
 4928    cx.assert_editor_state(indoc! {"
 4929        «ddddˇ»
 4930        ccc
 4931        bb
 4932        a
 4933    "});
 4934
 4935    // Basic manipulate case
 4936    // Start selection moves to column 0
 4937    // End of selection shrinks to fit shorter line
 4938    cx.set_state(indoc! {"
 4939        dd«d
 4940        ccc
 4941        bb
 4942        aaaaaˇ»
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4946    });
 4947    cx.assert_editor_state(indoc! {"
 4948        «aaaaa
 4949        bb
 4950        ccc
 4951        dddˇ»
 4952    "});
 4953
 4954    // Manipulate case with newlines
 4955    cx.set_state(indoc! {"
 4956        dd«d
 4957        ccc
 4958
 4959        bb
 4960        aaaaa
 4961
 4962        ˇ»
 4963    "});
 4964    cx.update_editor(|e, window, cx| {
 4965        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4966    });
 4967    cx.assert_editor_state(indoc! {"
 4968        «
 4969
 4970        aaaaa
 4971        bb
 4972        ccc
 4973        dddˇ»
 4974
 4975    "});
 4976
 4977    // Adding new line
 4978    cx.set_state(indoc! {"
 4979        aa«a
 4980        bbˇ»b
 4981    "});
 4982    cx.update_editor(|e, window, cx| {
 4983        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4984    });
 4985    cx.assert_editor_state(indoc! {"
 4986        «aaa
 4987        bbb
 4988        added_lineˇ»
 4989    "});
 4990
 4991    // Removing line
 4992    cx.set_state(indoc! {"
 4993        aa«a
 4994        bbbˇ»
 4995    "});
 4996    cx.update_editor(|e, window, cx| {
 4997        e.manipulate_immutable_lines(window, cx, |lines| {
 4998            lines.pop();
 4999        })
 5000    });
 5001    cx.assert_editor_state(indoc! {"
 5002        «aaaˇ»
 5003    "});
 5004
 5005    // Removing all lines
 5006    cx.set_state(indoc! {"
 5007        aa«a
 5008        bbbˇ»
 5009    "});
 5010    cx.update_editor(|e, window, cx| {
 5011        e.manipulate_immutable_lines(window, cx, |lines| {
 5012            lines.drain(..);
 5013        })
 5014    });
 5015    cx.assert_editor_state(indoc! {"
 5016        ˇ
 5017    "});
 5018}
 5019
 5020#[gpui::test]
 5021async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5022    init_test(cx, |_| {});
 5023
 5024    let mut cx = EditorTestContext::new(cx).await;
 5025
 5026    // Consider continuous selection as single selection
 5027    cx.set_state(indoc! {"
 5028        Aaa«aa
 5029        cˇ»c«c
 5030        bb
 5031        aaaˇ»aa
 5032    "});
 5033    cx.update_editor(|e, window, cx| {
 5034        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5035    });
 5036    cx.assert_editor_state(indoc! {"
 5037        «Aaaaa
 5038        ccc
 5039        bb
 5040        aaaaaˇ»
 5041    "});
 5042
 5043    cx.set_state(indoc! {"
 5044        Aaa«aa
 5045        cˇ»c«c
 5046        bb
 5047        aaaˇ»aa
 5048    "});
 5049    cx.update_editor(|e, window, cx| {
 5050        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5051    });
 5052    cx.assert_editor_state(indoc! {"
 5053        «Aaaaa
 5054        ccc
 5055        bbˇ»
 5056    "});
 5057
 5058    // Consider non continuous selection as distinct dedup operations
 5059    cx.set_state(indoc! {"
 5060        «aaaaa
 5061        bb
 5062        aaaaa
 5063        aaaaaˇ»
 5064
 5065        aaa«aaˇ»
 5066    "});
 5067    cx.update_editor(|e, window, cx| {
 5068        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5069    });
 5070    cx.assert_editor_state(indoc! {"
 5071        «aaaaa
 5072        bbˇ»
 5073
 5074        «aaaaaˇ»
 5075    "});
 5076}
 5077
 5078#[gpui::test]
 5079async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5080    init_test(cx, |_| {});
 5081
 5082    let mut cx = EditorTestContext::new(cx).await;
 5083
 5084    cx.set_state(indoc! {"
 5085        «Aaa
 5086        aAa
 5087        Aaaˇ»
 5088    "});
 5089    cx.update_editor(|e, window, cx| {
 5090        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5091    });
 5092    cx.assert_editor_state(indoc! {"
 5093        «Aaa
 5094        aAaˇ»
 5095    "});
 5096
 5097    cx.set_state(indoc! {"
 5098        «Aaa
 5099        aAa
 5100        aaAˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| {
 5103        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5104    });
 5105    cx.assert_editor_state(indoc! {"
 5106        «Aaaˇ»
 5107    "});
 5108}
 5109
 5110#[gpui::test]
 5111async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5112    init_test(cx, |_| {});
 5113
 5114    let mut cx = EditorTestContext::new(cx).await;
 5115
 5116    let js_language = Arc::new(Language::new(
 5117        LanguageConfig {
 5118            name: "JavaScript".into(),
 5119            wrap_characters: Some(language::WrapCharactersConfig {
 5120                start_prefix: "<".into(),
 5121                start_suffix: ">".into(),
 5122                end_prefix: "</".into(),
 5123                end_suffix: ">".into(),
 5124            }),
 5125            ..LanguageConfig::default()
 5126        },
 5127        None,
 5128    ));
 5129
 5130    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5131
 5132    cx.set_state(indoc! {"
 5133        «testˇ»
 5134    "});
 5135    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5136    cx.assert_editor_state(indoc! {"
 5137        <«ˇ»>test</«ˇ»>
 5138    "});
 5139
 5140    cx.set_state(indoc! {"
 5141        «test
 5142         testˇ»
 5143    "});
 5144    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5145    cx.assert_editor_state(indoc! {"
 5146        <«ˇ»>test
 5147         test</«ˇ»>
 5148    "});
 5149
 5150    cx.set_state(indoc! {"
 5151        teˇst
 5152    "});
 5153    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5154    cx.assert_editor_state(indoc! {"
 5155        te<«ˇ»></«ˇ»>st
 5156    "});
 5157}
 5158
 5159#[gpui::test]
 5160async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5161    init_test(cx, |_| {});
 5162
 5163    let mut cx = EditorTestContext::new(cx).await;
 5164
 5165    let js_language = Arc::new(Language::new(
 5166        LanguageConfig {
 5167            name: "JavaScript".into(),
 5168            wrap_characters: Some(language::WrapCharactersConfig {
 5169                start_prefix: "<".into(),
 5170                start_suffix: ">".into(),
 5171                end_prefix: "</".into(),
 5172                end_suffix: ">".into(),
 5173            }),
 5174            ..LanguageConfig::default()
 5175        },
 5176        None,
 5177    ));
 5178
 5179    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5180
 5181    cx.set_state(indoc! {"
 5182        «testˇ»
 5183        «testˇ» «testˇ»
 5184        «testˇ»
 5185    "});
 5186    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5187    cx.assert_editor_state(indoc! {"
 5188        <«ˇ»>test</«ˇ»>
 5189        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5190        <«ˇ»>test</«ˇ»>
 5191    "});
 5192
 5193    cx.set_state(indoc! {"
 5194        «test
 5195         testˇ»
 5196        «test
 5197         testˇ»
 5198    "});
 5199    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5200    cx.assert_editor_state(indoc! {"
 5201        <«ˇ»>test
 5202         test</«ˇ»>
 5203        <«ˇ»>test
 5204         test</«ˇ»>
 5205    "});
 5206}
 5207
 5208#[gpui::test]
 5209async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5210    init_test(cx, |_| {});
 5211
 5212    let mut cx = EditorTestContext::new(cx).await;
 5213
 5214    let plaintext_language = Arc::new(Language::new(
 5215        LanguageConfig {
 5216            name: "Plain Text".into(),
 5217            ..LanguageConfig::default()
 5218        },
 5219        None,
 5220    ));
 5221
 5222    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5223
 5224    cx.set_state(indoc! {"
 5225        «testˇ»
 5226    "});
 5227    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5228    cx.assert_editor_state(indoc! {"
 5229      «testˇ»
 5230    "});
 5231}
 5232
 5233#[gpui::test]
 5234async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5235    init_test(cx, |_| {});
 5236
 5237    let mut cx = EditorTestContext::new(cx).await;
 5238
 5239    // Manipulate with multiple selections on a single line
 5240    cx.set_state(indoc! {"
 5241        dd«dd
 5242        cˇ»c«c
 5243        bb
 5244        aaaˇ»aa
 5245    "});
 5246    cx.update_editor(|e, window, cx| {
 5247        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5248    });
 5249    cx.assert_editor_state(indoc! {"
 5250        «aaaaa
 5251        bb
 5252        ccc
 5253        ddddˇ»
 5254    "});
 5255
 5256    // Manipulate with multiple disjoin selections
 5257    cx.set_state(indoc! {"
 5258 5259        4
 5260        3
 5261        2
 5262        1ˇ»
 5263
 5264        dd«dd
 5265        ccc
 5266        bb
 5267        aaaˇ»aa
 5268    "});
 5269    cx.update_editor(|e, window, cx| {
 5270        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5271    });
 5272    cx.assert_editor_state(indoc! {"
 5273        «1
 5274        2
 5275        3
 5276        4
 5277        5ˇ»
 5278
 5279        «aaaaa
 5280        bb
 5281        ccc
 5282        ddddˇ»
 5283    "});
 5284
 5285    // Adding lines on each selection
 5286    cx.set_state(indoc! {"
 5287 5288        1ˇ»
 5289
 5290        bb«bb
 5291        aaaˇ»aa
 5292    "});
 5293    cx.update_editor(|e, window, cx| {
 5294        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5295    });
 5296    cx.assert_editor_state(indoc! {"
 5297        «2
 5298        1
 5299        added lineˇ»
 5300
 5301        «bbbb
 5302        aaaaa
 5303        added lineˇ»
 5304    "});
 5305
 5306    // Removing lines on each selection
 5307    cx.set_state(indoc! {"
 5308 5309        1ˇ»
 5310
 5311        bb«bb
 5312        aaaˇ»aa
 5313    "});
 5314    cx.update_editor(|e, window, cx| {
 5315        e.manipulate_immutable_lines(window, cx, |lines| {
 5316            lines.pop();
 5317        })
 5318    });
 5319    cx.assert_editor_state(indoc! {"
 5320        «2ˇ»
 5321
 5322        «bbbbˇ»
 5323    "});
 5324}
 5325
 5326#[gpui::test]
 5327async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5328    init_test(cx, |settings| {
 5329        settings.defaults.tab_size = NonZeroU32::new(3)
 5330    });
 5331
 5332    let mut cx = EditorTestContext::new(cx).await;
 5333
 5334    // MULTI SELECTION
 5335    // Ln.1 "«" tests empty lines
 5336    // Ln.9 tests just leading whitespace
 5337    cx.set_state(indoc! {"
 5338        «
 5339        abc                 // No indentationˇ»
 5340        «\tabc              // 1 tabˇ»
 5341        \t\tabc «      ˇ»   // 2 tabs
 5342        \t ab«c             // Tab followed by space
 5343         \tabc              // Space followed by tab (3 spaces should be the result)
 5344        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5345           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5346        \t
 5347        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5348    "});
 5349    cx.update_editor(|e, window, cx| {
 5350        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5351    });
 5352    cx.assert_editor_state(
 5353        indoc! {"
 5354            «
 5355            abc                 // No indentation
 5356               abc              // 1 tab
 5357                  abc          // 2 tabs
 5358                abc             // Tab followed by space
 5359               abc              // Space followed by tab (3 spaces should be the result)
 5360                           abc   // Mixed indentation (tab conversion depends on the column)
 5361               abc         // Already space indented
 5362               ·
 5363               abc\tdef          // Only the leading tab is manipulatedˇ»
 5364        "}
 5365        .replace("·", "")
 5366        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5367    );
 5368
 5369    // Test on just a few lines, the others should remain unchanged
 5370    // Only lines (3, 5, 10, 11) should change
 5371    cx.set_state(
 5372        indoc! {"
 5373            ·
 5374            abc                 // No indentation
 5375            \tabcˇ               // 1 tab
 5376            \t\tabc             // 2 tabs
 5377            \t abcˇ              // Tab followed by space
 5378             \tabc              // Space followed by tab (3 spaces should be the result)
 5379            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5380               abc              // Already space indented
 5381            «\t
 5382            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5383        "}
 5384        .replace("·", "")
 5385        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5386    );
 5387    cx.update_editor(|e, window, cx| {
 5388        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5389    });
 5390    cx.assert_editor_state(
 5391        indoc! {"
 5392            ·
 5393            abc                 // No indentation
 5394            «   abc               // 1 tabˇ»
 5395            \t\tabc             // 2 tabs
 5396            «    abc              // Tab followed by spaceˇ»
 5397             \tabc              // Space followed by tab (3 spaces should be the result)
 5398            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5399               abc              // Already space indented
 5400            «   ·
 5401               abc\tdef          // Only the leading tab is manipulatedˇ»
 5402        "}
 5403        .replace("·", "")
 5404        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5405    );
 5406
 5407    // SINGLE SELECTION
 5408    // Ln.1 "«" tests empty lines
 5409    // Ln.9 tests just leading whitespace
 5410    cx.set_state(indoc! {"
 5411        «
 5412        abc                 // No indentation
 5413        \tabc               // 1 tab
 5414        \t\tabc             // 2 tabs
 5415        \t abc              // Tab followed by space
 5416         \tabc              // Space followed by tab (3 spaces should be the result)
 5417        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5418           abc              // Already space indented
 5419        \t
 5420        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5421    "});
 5422    cx.update_editor(|e, window, cx| {
 5423        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5424    });
 5425    cx.assert_editor_state(
 5426        indoc! {"
 5427            «
 5428            abc                 // No indentation
 5429               abc               // 1 tab
 5430                  abc             // 2 tabs
 5431                abc              // Tab followed by space
 5432               abc              // Space followed by tab (3 spaces should be the result)
 5433                           abc   // Mixed indentation (tab conversion depends on the column)
 5434               abc              // Already space indented
 5435               ·
 5436               abc\tdef          // Only the leading tab is manipulatedˇ»
 5437        "}
 5438        .replace("·", "")
 5439        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5440    );
 5441}
 5442
 5443#[gpui::test]
 5444async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5445    init_test(cx, |settings| {
 5446        settings.defaults.tab_size = NonZeroU32::new(3)
 5447    });
 5448
 5449    let mut cx = EditorTestContext::new(cx).await;
 5450
 5451    // MULTI SELECTION
 5452    // Ln.1 "«" tests empty lines
 5453    // Ln.11 tests just leading whitespace
 5454    cx.set_state(indoc! {"
 5455        «
 5456        abˇ»ˇc                 // No indentation
 5457         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5458          abc  «             // 2 spaces (< 3 so dont convert)
 5459           abc              // 3 spaces (convert)
 5460             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5461        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5462        «\t abc              // Tab followed by space
 5463         \tabc              // Space followed by tab (should be consumed due to tab)
 5464        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5465           \tˇ»  «\t
 5466           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5467    "});
 5468    cx.update_editor(|e, window, cx| {
 5469        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5470    });
 5471    cx.assert_editor_state(indoc! {"
 5472        «
 5473        abc                 // No indentation
 5474         abc                // 1 space (< 3 so dont convert)
 5475          abc               // 2 spaces (< 3 so dont convert)
 5476        \tabc              // 3 spaces (convert)
 5477        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5478        \t\t\tabc           // Already tab indented
 5479        \t abc              // Tab followed by space
 5480        \tabc              // Space followed by tab (should be consumed due to tab)
 5481        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5482        \t\t\t
 5483        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5484    "});
 5485
 5486    // Test on just a few lines, the other should remain unchanged
 5487    // Only lines (4, 8, 11, 12) should change
 5488    cx.set_state(
 5489        indoc! {"
 5490            ·
 5491            abc                 // No indentation
 5492             abc                // 1 space (< 3 so dont convert)
 5493              abc               // 2 spaces (< 3 so dont convert)
 5494            «   abc              // 3 spaces (convert)ˇ»
 5495                 abc            // 5 spaces (1 tab + 2 spaces)
 5496            \t\t\tabc           // Already tab indented
 5497            \t abc              // Tab followed by space
 5498             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5499               \t\t  \tabc      // Mixed indentation
 5500            \t \t  \t   \tabc   // Mixed indentation
 5501               \t  \tˇ
 5502            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5503        "}
 5504        .replace("·", "")
 5505        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5506    );
 5507    cx.update_editor(|e, window, cx| {
 5508        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5509    });
 5510    cx.assert_editor_state(
 5511        indoc! {"
 5512            ·
 5513            abc                 // No indentation
 5514             abc                // 1 space (< 3 so dont convert)
 5515              abc               // 2 spaces (< 3 so dont convert)
 5516            «\tabc              // 3 spaces (convert)ˇ»
 5517                 abc            // 5 spaces (1 tab + 2 spaces)
 5518            \t\t\tabc           // Already tab indented
 5519            \t abc              // Tab followed by space
 5520            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5521               \t\t  \tabc      // Mixed indentation
 5522            \t \t  \t   \tabc   // Mixed indentation
 5523            «\t\t\t
 5524            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5525        "}
 5526        .replace("·", "")
 5527        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5528    );
 5529
 5530    // SINGLE SELECTION
 5531    // Ln.1 "«" tests empty lines
 5532    // Ln.11 tests just leading whitespace
 5533    cx.set_state(indoc! {"
 5534        «
 5535        abc                 // No indentation
 5536         abc                // 1 space (< 3 so dont convert)
 5537          abc               // 2 spaces (< 3 so dont convert)
 5538           abc              // 3 spaces (convert)
 5539             abc            // 5 spaces (1 tab + 2 spaces)
 5540        \t\t\tabc           // Already tab indented
 5541        \t abc              // Tab followed by space
 5542         \tabc              // Space followed by tab (should be consumed due to tab)
 5543        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5544           \t  \t
 5545           abc   \t         // Only the leading spaces should be convertedˇ»
 5546    "});
 5547    cx.update_editor(|e, window, cx| {
 5548        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5549    });
 5550    cx.assert_editor_state(indoc! {"
 5551        «
 5552        abc                 // No indentation
 5553         abc                // 1 space (< 3 so dont convert)
 5554          abc               // 2 spaces (< 3 so dont convert)
 5555        \tabc              // 3 spaces (convert)
 5556        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5557        \t\t\tabc           // Already tab indented
 5558        \t abc              // Tab followed by space
 5559        \tabc              // Space followed by tab (should be consumed due to tab)
 5560        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5561        \t\t\t
 5562        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5563    "});
 5564}
 5565
 5566#[gpui::test]
 5567async fn test_toggle_case(cx: &mut TestAppContext) {
 5568    init_test(cx, |_| {});
 5569
 5570    let mut cx = EditorTestContext::new(cx).await;
 5571
 5572    // If all lower case -> upper case
 5573    cx.set_state(indoc! {"
 5574        «hello worldˇ»
 5575    "});
 5576    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5577    cx.assert_editor_state(indoc! {"
 5578        «HELLO WORLDˇ»
 5579    "});
 5580
 5581    // If all upper case -> lower case
 5582    cx.set_state(indoc! {"
 5583        «HELLO WORLDˇ»
 5584    "});
 5585    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5586    cx.assert_editor_state(indoc! {"
 5587        «hello worldˇ»
 5588    "});
 5589
 5590    // If any upper case characters are identified -> lower case
 5591    // This matches JetBrains IDEs
 5592    cx.set_state(indoc! {"
 5593        «hEllo worldˇ»
 5594    "});
 5595    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5596    cx.assert_editor_state(indoc! {"
 5597        «hello worldˇ»
 5598    "});
 5599}
 5600
 5601#[gpui::test]
 5602async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5603    init_test(cx, |_| {});
 5604
 5605    let mut cx = EditorTestContext::new(cx).await;
 5606
 5607    cx.set_state(indoc! {"
 5608        «implement-windows-supportˇ»
 5609    "});
 5610    cx.update_editor(|e, window, cx| {
 5611        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5612    });
 5613    cx.assert_editor_state(indoc! {"
 5614        «Implement windows supportˇ»
 5615    "});
 5616}
 5617
 5618#[gpui::test]
 5619async fn test_manipulate_text(cx: &mut TestAppContext) {
 5620    init_test(cx, |_| {});
 5621
 5622    let mut cx = EditorTestContext::new(cx).await;
 5623
 5624    // Test convert_to_upper_case()
 5625    cx.set_state(indoc! {"
 5626        «hello worldˇ»
 5627    "});
 5628    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5629    cx.assert_editor_state(indoc! {"
 5630        «HELLO WORLDˇ»
 5631    "});
 5632
 5633    // Test convert_to_lower_case()
 5634    cx.set_state(indoc! {"
 5635        «HELLO WORLDˇ»
 5636    "});
 5637    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5638    cx.assert_editor_state(indoc! {"
 5639        «hello worldˇ»
 5640    "});
 5641
 5642    // Test multiple line, single selection case
 5643    cx.set_state(indoc! {"
 5644        «The quick brown
 5645        fox jumps over
 5646        the lazy dogˇ»
 5647    "});
 5648    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5649    cx.assert_editor_state(indoc! {"
 5650        «The Quick Brown
 5651        Fox Jumps Over
 5652        The Lazy Dogˇ»
 5653    "});
 5654
 5655    // Test multiple line, single selection case
 5656    cx.set_state(indoc! {"
 5657        «The quick brown
 5658        fox jumps over
 5659        the lazy dogˇ»
 5660    "});
 5661    cx.update_editor(|e, window, cx| {
 5662        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5663    });
 5664    cx.assert_editor_state(indoc! {"
 5665        «TheQuickBrown
 5666        FoxJumpsOver
 5667        TheLazyDogˇ»
 5668    "});
 5669
 5670    // From here on out, test more complex cases of manipulate_text()
 5671
 5672    // Test no selection case - should affect words cursors are in
 5673    // Cursor at beginning, middle, and end of word
 5674    cx.set_state(indoc! {"
 5675        ˇhello big beauˇtiful worldˇ
 5676    "});
 5677    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5678    cx.assert_editor_state(indoc! {"
 5679        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5680    "});
 5681
 5682    // Test multiple selections on a single line and across multiple lines
 5683    cx.set_state(indoc! {"
 5684        «Theˇ» quick «brown
 5685        foxˇ» jumps «overˇ»
 5686        the «lazyˇ» dog
 5687    "});
 5688    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5689    cx.assert_editor_state(indoc! {"
 5690        «THEˇ» quick «BROWN
 5691        FOXˇ» jumps «OVERˇ»
 5692        the «LAZYˇ» dog
 5693    "});
 5694
 5695    // Test case where text length grows
 5696    cx.set_state(indoc! {"
 5697        «tschüߡ»
 5698    "});
 5699    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5700    cx.assert_editor_state(indoc! {"
 5701        «TSCHÜSSˇ»
 5702    "});
 5703
 5704    // Test to make sure we don't crash when text shrinks
 5705    cx.set_state(indoc! {"
 5706        aaa_bbbˇ
 5707    "});
 5708    cx.update_editor(|e, window, cx| {
 5709        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5710    });
 5711    cx.assert_editor_state(indoc! {"
 5712        «aaaBbbˇ»
 5713    "});
 5714
 5715    // Test to make sure we all aware of the fact that each word can grow and shrink
 5716    // Final selections should be aware of this fact
 5717    cx.set_state(indoc! {"
 5718        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5719    "});
 5720    cx.update_editor(|e, window, cx| {
 5721        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5722    });
 5723    cx.assert_editor_state(indoc! {"
 5724        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5725    "});
 5726
 5727    cx.set_state(indoc! {"
 5728        «hElLo, WoRld!ˇ»
 5729    "});
 5730    cx.update_editor(|e, window, cx| {
 5731        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5732    });
 5733    cx.assert_editor_state(indoc! {"
 5734        «HeLlO, wOrLD!ˇ»
 5735    "});
 5736
 5737    // Test selections with `line_mode() = true`.
 5738    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5739    cx.set_state(indoc! {"
 5740        «The quick brown
 5741        fox jumps over
 5742        tˇ»he lazy dog
 5743    "});
 5744    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5745    cx.assert_editor_state(indoc! {"
 5746        «THE QUICK BROWN
 5747        FOX JUMPS OVER
 5748        THE LAZY DOGˇ»
 5749    "});
 5750}
 5751
 5752#[gpui::test]
 5753fn test_duplicate_line(cx: &mut TestAppContext) {
 5754    init_test(cx, |_| {});
 5755
 5756    let editor = cx.add_window(|window, cx| {
 5757        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5758        build_editor(buffer, window, cx)
 5759    });
 5760    _ = editor.update(cx, |editor, window, cx| {
 5761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5762            s.select_display_ranges([
 5763                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5764                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5765                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5766                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5767            ])
 5768        });
 5769        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5770        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5771        assert_eq!(
 5772            display_ranges(editor, cx),
 5773            vec![
 5774                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5775                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5776                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5777                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5778            ]
 5779        );
 5780    });
 5781
 5782    let editor = cx.add_window(|window, cx| {
 5783        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5784        build_editor(buffer, window, cx)
 5785    });
 5786    _ = editor.update(cx, |editor, window, cx| {
 5787        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5788            s.select_display_ranges([
 5789                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5790                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5791            ])
 5792        });
 5793        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5794        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5795        assert_eq!(
 5796            display_ranges(editor, cx),
 5797            vec![
 5798                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5799                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5800            ]
 5801        );
 5802    });
 5803
 5804    // With `duplicate_line_up` the selections move to the duplicated lines,
 5805    // which are inserted above the original lines
 5806    let editor = cx.add_window(|window, cx| {
 5807        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5808        build_editor(buffer, window, cx)
 5809    });
 5810    _ = editor.update(cx, |editor, window, cx| {
 5811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5812            s.select_display_ranges([
 5813                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5814                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5815                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5816                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5817            ])
 5818        });
 5819        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5820        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5821        assert_eq!(
 5822            display_ranges(editor, cx),
 5823            vec![
 5824                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5825                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5826                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5827                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5828            ]
 5829        );
 5830    });
 5831
 5832    let editor = cx.add_window(|window, cx| {
 5833        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5834        build_editor(buffer, window, cx)
 5835    });
 5836    _ = editor.update(cx, |editor, window, cx| {
 5837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5838            s.select_display_ranges([
 5839                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5840                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5841            ])
 5842        });
 5843        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5844        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5845        assert_eq!(
 5846            display_ranges(editor, cx),
 5847            vec![
 5848                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5849                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5850            ]
 5851        );
 5852    });
 5853
 5854    let editor = cx.add_window(|window, cx| {
 5855        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5856        build_editor(buffer, window, cx)
 5857    });
 5858    _ = editor.update(cx, |editor, window, cx| {
 5859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5860            s.select_display_ranges([
 5861                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5862                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5863            ])
 5864        });
 5865        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5866        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5867        assert_eq!(
 5868            display_ranges(editor, cx),
 5869            vec![
 5870                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5871                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5872            ]
 5873        );
 5874    });
 5875}
 5876
 5877#[gpui::test]
 5878async fn test_rotate_selections(cx: &mut TestAppContext) {
 5879    init_test(cx, |_| {});
 5880
 5881    let mut cx = EditorTestContext::new(cx).await;
 5882
 5883    // Rotate text selections (horizontal)
 5884    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5885    cx.update_editor(|e, window, cx| {
 5886        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5887    });
 5888    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5889    cx.update_editor(|e, window, cx| {
 5890        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5891    });
 5892    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5893
 5894    // Rotate text selections (vertical)
 5895    cx.set_state(indoc! {"
 5896        x=«1ˇ»
 5897        y=«2ˇ»
 5898        z=«3ˇ»
 5899    "});
 5900    cx.update_editor(|e, window, cx| {
 5901        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5902    });
 5903    cx.assert_editor_state(indoc! {"
 5904        x=«3ˇ»
 5905        y=«1ˇ»
 5906        z=«2ˇ»
 5907    "});
 5908    cx.update_editor(|e, window, cx| {
 5909        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5910    });
 5911    cx.assert_editor_state(indoc! {"
 5912        x=«1ˇ»
 5913        y=«2ˇ»
 5914        z=«3ˇ»
 5915    "});
 5916
 5917    // Rotate text selections (vertical, different lengths)
 5918    cx.set_state(indoc! {"
 5919        x=\"«ˇ»\"
 5920        y=\"«aˇ»\"
 5921        z=\"«aaˇ»\"
 5922    "});
 5923    cx.update_editor(|e, window, cx| {
 5924        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5925    });
 5926    cx.assert_editor_state(indoc! {"
 5927        x=\"«aaˇ»\"
 5928        y=\"«ˇ»\"
 5929        z=\"«aˇ»\"
 5930    "});
 5931    cx.update_editor(|e, window, cx| {
 5932        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5933    });
 5934    cx.assert_editor_state(indoc! {"
 5935        x=\"«ˇ»\"
 5936        y=\"«aˇ»\"
 5937        z=\"«aaˇ»\"
 5938    "});
 5939
 5940    // Rotate whole lines (cursor positions preserved)
 5941    cx.set_state(indoc! {"
 5942        ˇline123
 5943        liˇne23
 5944        line3ˇ
 5945    "});
 5946    cx.update_editor(|e, window, cx| {
 5947        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5948    });
 5949    cx.assert_editor_state(indoc! {"
 5950        line3ˇ
 5951        ˇline123
 5952        liˇne23
 5953    "});
 5954    cx.update_editor(|e, window, cx| {
 5955        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5956    });
 5957    cx.assert_editor_state(indoc! {"
 5958        ˇline123
 5959        liˇne23
 5960        line3ˇ
 5961    "});
 5962
 5963    // Rotate whole lines, multiple cursors per line (positions preserved)
 5964    cx.set_state(indoc! {"
 5965        ˇliˇne123
 5966        ˇline23
 5967        ˇline3
 5968    "});
 5969    cx.update_editor(|e, window, cx| {
 5970        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5971    });
 5972    cx.assert_editor_state(indoc! {"
 5973        ˇline3
 5974        ˇliˇne123
 5975        ˇline23
 5976    "});
 5977    cx.update_editor(|e, window, cx| {
 5978        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5979    });
 5980    cx.assert_editor_state(indoc! {"
 5981        ˇliˇne123
 5982        ˇline23
 5983        ˇline3
 5984    "});
 5985}
 5986
 5987#[gpui::test]
 5988fn test_move_line_up_down(cx: &mut TestAppContext) {
 5989    init_test(cx, |_| {});
 5990
 5991    let editor = cx.add_window(|window, cx| {
 5992        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5993        build_editor(buffer, window, cx)
 5994    });
 5995    _ = editor.update(cx, |editor, window, cx| {
 5996        editor.fold_creases(
 5997            vec![
 5998                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5999                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6000                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6001            ],
 6002            true,
 6003            window,
 6004            cx,
 6005        );
 6006        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6007            s.select_display_ranges([
 6008                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6009                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6010                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6011                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 6012            ])
 6013        });
 6014        assert_eq!(
 6015            editor.display_text(cx),
 6016            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6017        );
 6018
 6019        editor.move_line_up(&MoveLineUp, window, cx);
 6020        assert_eq!(
 6021            editor.display_text(cx),
 6022            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6023        );
 6024        assert_eq!(
 6025            display_ranges(editor, cx),
 6026            vec![
 6027                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6028                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6029                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6030                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6031            ]
 6032        );
 6033    });
 6034
 6035    _ = editor.update(cx, |editor, window, cx| {
 6036        editor.move_line_down(&MoveLineDown, window, cx);
 6037        assert_eq!(
 6038            editor.display_text(cx),
 6039            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6040        );
 6041        assert_eq!(
 6042            display_ranges(editor, cx),
 6043            vec![
 6044                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6045                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6046                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6047                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6048            ]
 6049        );
 6050    });
 6051
 6052    _ = editor.update(cx, |editor, window, cx| {
 6053        editor.move_line_down(&MoveLineDown, window, cx);
 6054        assert_eq!(
 6055            editor.display_text(cx),
 6056            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6057        );
 6058        assert_eq!(
 6059            display_ranges(editor, cx),
 6060            vec![
 6061                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6062                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6063                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6064                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6065            ]
 6066        );
 6067    });
 6068
 6069    _ = editor.update(cx, |editor, window, cx| {
 6070        editor.move_line_up(&MoveLineUp, window, cx);
 6071        assert_eq!(
 6072            editor.display_text(cx),
 6073            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6074        );
 6075        assert_eq!(
 6076            display_ranges(editor, cx),
 6077            vec![
 6078                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6079                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6080                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6081                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6082            ]
 6083        );
 6084    });
 6085}
 6086
 6087#[gpui::test]
 6088fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6089    init_test(cx, |_| {});
 6090    let editor = cx.add_window(|window, cx| {
 6091        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6092        build_editor(buffer, window, cx)
 6093    });
 6094    _ = editor.update(cx, |editor, window, cx| {
 6095        editor.fold_creases(
 6096            vec![Crease::simple(
 6097                Point::new(6, 4)..Point::new(7, 4),
 6098                FoldPlaceholder::test(),
 6099            )],
 6100            true,
 6101            window,
 6102            cx,
 6103        );
 6104        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6105            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6106        });
 6107        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6108        editor.move_line_up(&MoveLineUp, window, cx);
 6109        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6110        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6111    });
 6112}
 6113
 6114#[gpui::test]
 6115fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6116    init_test(cx, |_| {});
 6117
 6118    let editor = cx.add_window(|window, cx| {
 6119        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6120        build_editor(buffer, window, cx)
 6121    });
 6122    _ = editor.update(cx, |editor, window, cx| {
 6123        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6124        editor.insert_blocks(
 6125            [BlockProperties {
 6126                style: BlockStyle::Fixed,
 6127                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6128                height: Some(1),
 6129                render: Arc::new(|_| div().into_any()),
 6130                priority: 0,
 6131            }],
 6132            Some(Autoscroll::fit()),
 6133            cx,
 6134        );
 6135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6136            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6137        });
 6138        editor.move_line_down(&MoveLineDown, window, cx);
 6139    });
 6140}
 6141
 6142#[gpui::test]
 6143async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6144    init_test(cx, |_| {});
 6145
 6146    let mut cx = EditorTestContext::new(cx).await;
 6147    cx.set_state(
 6148        &"
 6149            ˇzero
 6150            one
 6151            two
 6152            three
 6153            four
 6154            five
 6155        "
 6156        .unindent(),
 6157    );
 6158
 6159    // Create a four-line block that replaces three lines of text.
 6160    cx.update_editor(|editor, window, cx| {
 6161        let snapshot = editor.snapshot(window, cx);
 6162        let snapshot = &snapshot.buffer_snapshot();
 6163        let placement = BlockPlacement::Replace(
 6164            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6165        );
 6166        editor.insert_blocks(
 6167            [BlockProperties {
 6168                placement,
 6169                height: Some(4),
 6170                style: BlockStyle::Sticky,
 6171                render: Arc::new(|_| gpui::div().into_any_element()),
 6172                priority: 0,
 6173            }],
 6174            None,
 6175            cx,
 6176        );
 6177    });
 6178
 6179    // Move down so that the cursor touches the block.
 6180    cx.update_editor(|editor, window, cx| {
 6181        editor.move_down(&Default::default(), window, cx);
 6182    });
 6183    cx.assert_editor_state(
 6184        &"
 6185            zero
 6186            «one
 6187            two
 6188            threeˇ»
 6189            four
 6190            five
 6191        "
 6192        .unindent(),
 6193    );
 6194
 6195    // Move down past the block.
 6196    cx.update_editor(|editor, window, cx| {
 6197        editor.move_down(&Default::default(), window, cx);
 6198    });
 6199    cx.assert_editor_state(
 6200        &"
 6201            zero
 6202            one
 6203            two
 6204            three
 6205            ˇfour
 6206            five
 6207        "
 6208        .unindent(),
 6209    );
 6210}
 6211
 6212#[gpui::test]
 6213fn test_transpose(cx: &mut TestAppContext) {
 6214    init_test(cx, |_| {});
 6215
 6216    _ = cx.add_window(|window, cx| {
 6217        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6218        editor.set_style(EditorStyle::default(), window, cx);
 6219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6220            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6221        });
 6222        editor.transpose(&Default::default(), window, cx);
 6223        assert_eq!(editor.text(cx), "bac");
 6224        assert_eq!(
 6225            editor.selections.ranges(&editor.display_snapshot(cx)),
 6226            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6227        );
 6228
 6229        editor.transpose(&Default::default(), window, cx);
 6230        assert_eq!(editor.text(cx), "bca");
 6231        assert_eq!(
 6232            editor.selections.ranges(&editor.display_snapshot(cx)),
 6233            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6234        );
 6235
 6236        editor.transpose(&Default::default(), window, cx);
 6237        assert_eq!(editor.text(cx), "bac");
 6238        assert_eq!(
 6239            editor.selections.ranges(&editor.display_snapshot(cx)),
 6240            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6241        );
 6242
 6243        editor
 6244    });
 6245
 6246    _ = cx.add_window(|window, cx| {
 6247        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6248        editor.set_style(EditorStyle::default(), window, cx);
 6249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6250            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6251        });
 6252        editor.transpose(&Default::default(), window, cx);
 6253        assert_eq!(editor.text(cx), "acb\nde");
 6254        assert_eq!(
 6255            editor.selections.ranges(&editor.display_snapshot(cx)),
 6256            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6257        );
 6258
 6259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6260            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6261        });
 6262        editor.transpose(&Default::default(), window, cx);
 6263        assert_eq!(editor.text(cx), "acbd\ne");
 6264        assert_eq!(
 6265            editor.selections.ranges(&editor.display_snapshot(cx)),
 6266            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6267        );
 6268
 6269        editor.transpose(&Default::default(), window, cx);
 6270        assert_eq!(editor.text(cx), "acbde\n");
 6271        assert_eq!(
 6272            editor.selections.ranges(&editor.display_snapshot(cx)),
 6273            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6274        );
 6275
 6276        editor.transpose(&Default::default(), window, cx);
 6277        assert_eq!(editor.text(cx), "acbd\ne");
 6278        assert_eq!(
 6279            editor.selections.ranges(&editor.display_snapshot(cx)),
 6280            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6281        );
 6282
 6283        editor
 6284    });
 6285
 6286    _ = cx.add_window(|window, cx| {
 6287        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6288        editor.set_style(EditorStyle::default(), window, cx);
 6289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6290            s.select_ranges([
 6291                MultiBufferOffset(1)..MultiBufferOffset(1),
 6292                MultiBufferOffset(2)..MultiBufferOffset(2),
 6293                MultiBufferOffset(4)..MultiBufferOffset(4),
 6294            ])
 6295        });
 6296        editor.transpose(&Default::default(), window, cx);
 6297        assert_eq!(editor.text(cx), "bacd\ne");
 6298        assert_eq!(
 6299            editor.selections.ranges(&editor.display_snapshot(cx)),
 6300            [
 6301                MultiBufferOffset(2)..MultiBufferOffset(2),
 6302                MultiBufferOffset(3)..MultiBufferOffset(3),
 6303                MultiBufferOffset(5)..MultiBufferOffset(5)
 6304            ]
 6305        );
 6306
 6307        editor.transpose(&Default::default(), window, cx);
 6308        assert_eq!(editor.text(cx), "bcade\n");
 6309        assert_eq!(
 6310            editor.selections.ranges(&editor.display_snapshot(cx)),
 6311            [
 6312                MultiBufferOffset(3)..MultiBufferOffset(3),
 6313                MultiBufferOffset(4)..MultiBufferOffset(4),
 6314                MultiBufferOffset(6)..MultiBufferOffset(6)
 6315            ]
 6316        );
 6317
 6318        editor.transpose(&Default::default(), window, cx);
 6319        assert_eq!(editor.text(cx), "bcda\ne");
 6320        assert_eq!(
 6321            editor.selections.ranges(&editor.display_snapshot(cx)),
 6322            [
 6323                MultiBufferOffset(4)..MultiBufferOffset(4),
 6324                MultiBufferOffset(6)..MultiBufferOffset(6)
 6325            ]
 6326        );
 6327
 6328        editor.transpose(&Default::default(), window, cx);
 6329        assert_eq!(editor.text(cx), "bcade\n");
 6330        assert_eq!(
 6331            editor.selections.ranges(&editor.display_snapshot(cx)),
 6332            [
 6333                MultiBufferOffset(4)..MultiBufferOffset(4),
 6334                MultiBufferOffset(6)..MultiBufferOffset(6)
 6335            ]
 6336        );
 6337
 6338        editor.transpose(&Default::default(), window, cx);
 6339        assert_eq!(editor.text(cx), "bcaed\n");
 6340        assert_eq!(
 6341            editor.selections.ranges(&editor.display_snapshot(cx)),
 6342            [
 6343                MultiBufferOffset(5)..MultiBufferOffset(5),
 6344                MultiBufferOffset(6)..MultiBufferOffset(6)
 6345            ]
 6346        );
 6347
 6348        editor
 6349    });
 6350
 6351    _ = cx.add_window(|window, cx| {
 6352        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6353        editor.set_style(EditorStyle::default(), window, cx);
 6354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6355            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6356        });
 6357        editor.transpose(&Default::default(), window, cx);
 6358        assert_eq!(editor.text(cx), "🏀🍐✋");
 6359        assert_eq!(
 6360            editor.selections.ranges(&editor.display_snapshot(cx)),
 6361            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6362        );
 6363
 6364        editor.transpose(&Default::default(), window, cx);
 6365        assert_eq!(editor.text(cx), "🏀✋🍐");
 6366        assert_eq!(
 6367            editor.selections.ranges(&editor.display_snapshot(cx)),
 6368            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6369        );
 6370
 6371        editor.transpose(&Default::default(), window, cx);
 6372        assert_eq!(editor.text(cx), "🏀🍐✋");
 6373        assert_eq!(
 6374            editor.selections.ranges(&editor.display_snapshot(cx)),
 6375            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6376        );
 6377
 6378        editor
 6379    });
 6380}
 6381
 6382#[gpui::test]
 6383async fn test_rewrap(cx: &mut TestAppContext) {
 6384    init_test(cx, |settings| {
 6385        settings.languages.0.extend([
 6386            (
 6387                "Markdown".into(),
 6388                LanguageSettingsContent {
 6389                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6390                    preferred_line_length: Some(40),
 6391                    ..Default::default()
 6392                },
 6393            ),
 6394            (
 6395                "Plain Text".into(),
 6396                LanguageSettingsContent {
 6397                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6398                    preferred_line_length: Some(40),
 6399                    ..Default::default()
 6400                },
 6401            ),
 6402            (
 6403                "C++".into(),
 6404                LanguageSettingsContent {
 6405                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6406                    preferred_line_length: Some(40),
 6407                    ..Default::default()
 6408                },
 6409            ),
 6410            (
 6411                "Python".into(),
 6412                LanguageSettingsContent {
 6413                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6414                    preferred_line_length: Some(40),
 6415                    ..Default::default()
 6416                },
 6417            ),
 6418            (
 6419                "Rust".into(),
 6420                LanguageSettingsContent {
 6421                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6422                    preferred_line_length: Some(40),
 6423                    ..Default::default()
 6424                },
 6425            ),
 6426        ])
 6427    });
 6428
 6429    let mut cx = EditorTestContext::new(cx).await;
 6430
 6431    let cpp_language = Arc::new(Language::new(
 6432        LanguageConfig {
 6433            name: "C++".into(),
 6434            line_comments: vec!["// ".into()],
 6435            ..LanguageConfig::default()
 6436        },
 6437        None,
 6438    ));
 6439    let python_language = Arc::new(Language::new(
 6440        LanguageConfig {
 6441            name: "Python".into(),
 6442            line_comments: vec!["# ".into()],
 6443            ..LanguageConfig::default()
 6444        },
 6445        None,
 6446    ));
 6447    let markdown_language = Arc::new(Language::new(
 6448        LanguageConfig {
 6449            name: "Markdown".into(),
 6450            rewrap_prefixes: vec![
 6451                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6452                regex::Regex::new("[-*+]\\s+").unwrap(),
 6453            ],
 6454            ..LanguageConfig::default()
 6455        },
 6456        None,
 6457    ));
 6458    let rust_language = Arc::new(
 6459        Language::new(
 6460            LanguageConfig {
 6461                name: "Rust".into(),
 6462                line_comments: vec!["// ".into(), "/// ".into()],
 6463                ..LanguageConfig::default()
 6464            },
 6465            Some(tree_sitter_rust::LANGUAGE.into()),
 6466        )
 6467        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6468        .unwrap(),
 6469    );
 6470
 6471    let plaintext_language = Arc::new(Language::new(
 6472        LanguageConfig {
 6473            name: "Plain Text".into(),
 6474            ..LanguageConfig::default()
 6475        },
 6476        None,
 6477    ));
 6478
 6479    // Test basic rewrapping of a long line with a cursor
 6480    assert_rewrap(
 6481        indoc! {"
 6482            // ˇThis is a long comment that needs to be wrapped.
 6483        "},
 6484        indoc! {"
 6485            // ˇThis is a long comment that needs to
 6486            // be wrapped.
 6487        "},
 6488        cpp_language.clone(),
 6489        &mut cx,
 6490    );
 6491
 6492    // Test rewrapping a full selection
 6493    assert_rewrap(
 6494        indoc! {"
 6495            «// This selected long comment needs to be wrapped.ˇ»"
 6496        },
 6497        indoc! {"
 6498            «// This selected long comment needs to
 6499            // be wrapped.ˇ»"
 6500        },
 6501        cpp_language.clone(),
 6502        &mut cx,
 6503    );
 6504
 6505    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6506    assert_rewrap(
 6507        indoc! {"
 6508            // ˇThis is the first line.
 6509            // Thisˇ is the second line.
 6510            // This is the thirdˇ line, all part of one paragraph.
 6511         "},
 6512        indoc! {"
 6513            // ˇThis is the first line. Thisˇ is the
 6514            // second line. This is the thirdˇ line,
 6515            // all part of one paragraph.
 6516         "},
 6517        cpp_language.clone(),
 6518        &mut cx,
 6519    );
 6520
 6521    // Test multiple cursors in different paragraphs trigger separate rewraps
 6522    assert_rewrap(
 6523        indoc! {"
 6524            // ˇThis is the first paragraph, first line.
 6525            // ˇThis is the first paragraph, second line.
 6526
 6527            // ˇThis is the second paragraph, first line.
 6528            // ˇThis is the second paragraph, second line.
 6529        "},
 6530        indoc! {"
 6531            // ˇThis is the first paragraph, first
 6532            // line. ˇThis is the first paragraph,
 6533            // second line.
 6534
 6535            // ˇThis is the second paragraph, first
 6536            // line. ˇThis is the second paragraph,
 6537            // second line.
 6538        "},
 6539        cpp_language.clone(),
 6540        &mut cx,
 6541    );
 6542
 6543    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6544    assert_rewrap(
 6545        indoc! {"
 6546            «// A regular long long comment to be wrapped.
 6547            /// A documentation long comment to be wrapped.ˇ»
 6548          "},
 6549        indoc! {"
 6550            «// A regular long long comment to be
 6551            // wrapped.
 6552            /// A documentation long comment to be
 6553            /// wrapped.ˇ»
 6554          "},
 6555        rust_language.clone(),
 6556        &mut cx,
 6557    );
 6558
 6559    // Test that change in indentation level trigger seperate rewraps
 6560    assert_rewrap(
 6561        indoc! {"
 6562            fn foo() {
 6563                «// This is a long comment at the base indent.
 6564                    // This is a long comment at the next indent.ˇ»
 6565            }
 6566        "},
 6567        indoc! {"
 6568            fn foo() {
 6569                «// This is a long comment at the
 6570                // base indent.
 6571                    // This is a long comment at the
 6572                    // next indent.ˇ»
 6573            }
 6574        "},
 6575        rust_language.clone(),
 6576        &mut cx,
 6577    );
 6578
 6579    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6580    assert_rewrap(
 6581        indoc! {"
 6582            # ˇThis is a long comment using a pound sign.
 6583        "},
 6584        indoc! {"
 6585            # ˇThis is a long comment using a pound
 6586            # sign.
 6587        "},
 6588        python_language,
 6589        &mut cx,
 6590    );
 6591
 6592    // Test rewrapping only affects comments, not code even when selected
 6593    assert_rewrap(
 6594        indoc! {"
 6595            «/// This doc comment is long and should be wrapped.
 6596            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6597        "},
 6598        indoc! {"
 6599            «/// This doc comment is long and should
 6600            /// be wrapped.
 6601            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6602        "},
 6603        rust_language.clone(),
 6604        &mut cx,
 6605    );
 6606
 6607    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6608    assert_rewrap(
 6609        indoc! {"
 6610            # Header
 6611
 6612            A long long long line of markdown text to wrap.ˇ
 6613         "},
 6614        indoc! {"
 6615            # Header
 6616
 6617            A long long long line of markdown text
 6618            to wrap.ˇ
 6619         "},
 6620        markdown_language.clone(),
 6621        &mut cx,
 6622    );
 6623
 6624    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6625    assert_rewrap(
 6626        indoc! {"
 6627            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6628            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6629            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6630        "},
 6631        indoc! {"
 6632            «1. This is a numbered list item that is
 6633               very long and needs to be wrapped
 6634               properly.
 6635            2. This is a numbered list item that is
 6636               very long and needs to be wrapped
 6637               properly.
 6638            - This is an unordered list item that is
 6639              also very long and should not merge
 6640              with the numbered item.ˇ»
 6641        "},
 6642        markdown_language.clone(),
 6643        &mut cx,
 6644    );
 6645
 6646    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6647    assert_rewrap(
 6648        indoc! {"
 6649            «1. This is a numbered list item that is
 6650            very long and needs to be wrapped
 6651            properly.
 6652            2. This is a numbered list item that is
 6653            very long and needs to be wrapped
 6654            properly.
 6655            - This is an unordered list item that is
 6656            also very long and should not merge with
 6657            the numbered item.ˇ»
 6658        "},
 6659        indoc! {"
 6660            «1. This is a numbered list item that is
 6661               very long and needs to be wrapped
 6662               properly.
 6663            2. This is a numbered list item that is
 6664               very long and needs to be wrapped
 6665               properly.
 6666            - This is an unordered list item that is
 6667              also very long and should not merge
 6668              with the numbered item.ˇ»
 6669        "},
 6670        markdown_language.clone(),
 6671        &mut cx,
 6672    );
 6673
 6674    // Test that rewrapping maintain indents even when they already exists.
 6675    assert_rewrap(
 6676        indoc! {"
 6677            «1. This is a numbered list
 6678               item that is very long and needs to be wrapped properly.
 6679            2. This is a numbered list
 6680               item that is very long and needs to be wrapped properly.
 6681            - This is an unordered list item that is also very long and
 6682              should not merge with the numbered item.ˇ»
 6683        "},
 6684        indoc! {"
 6685            «1. This is a numbered list item that is
 6686               very long and needs to be wrapped
 6687               properly.
 6688            2. This is a numbered list item that is
 6689               very long and needs to be wrapped
 6690               properly.
 6691            - This is an unordered list item that is
 6692              also very long and should not merge
 6693              with the numbered item.ˇ»
 6694        "},
 6695        markdown_language,
 6696        &mut cx,
 6697    );
 6698
 6699    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6700    assert_rewrap(
 6701        indoc! {"
 6702            ˇThis is a very long line of plain text that will be wrapped.
 6703        "},
 6704        indoc! {"
 6705            ˇThis is a very long line of plain text
 6706            that will be wrapped.
 6707        "},
 6708        plaintext_language.clone(),
 6709        &mut cx,
 6710    );
 6711
 6712    // Test that non-commented code acts as a paragraph boundary within a selection
 6713    assert_rewrap(
 6714        indoc! {"
 6715               «// This is the first long comment block to be wrapped.
 6716               fn my_func(a: u32);
 6717               // This is the second long comment block to be wrapped.ˇ»
 6718           "},
 6719        indoc! {"
 6720               «// This is the first long comment block
 6721               // to be wrapped.
 6722               fn my_func(a: u32);
 6723               // This is the second long comment block
 6724               // to be wrapped.ˇ»
 6725           "},
 6726        rust_language,
 6727        &mut cx,
 6728    );
 6729
 6730    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6731    assert_rewrap(
 6732        indoc! {"
 6733            «ˇThis is a very long line that will be wrapped.
 6734
 6735            This is another paragraph in the same selection.»
 6736
 6737            «\tThis is a very long indented line that will be wrapped.ˇ»
 6738         "},
 6739        indoc! {"
 6740            «ˇThis is a very long line that will be
 6741            wrapped.
 6742
 6743            This is another paragraph in the same
 6744            selection.»
 6745
 6746            «\tThis is a very long indented line
 6747            \tthat will be wrapped.ˇ»
 6748         "},
 6749        plaintext_language,
 6750        &mut cx,
 6751    );
 6752
 6753    // Test that an empty comment line acts as a paragraph boundary
 6754    assert_rewrap(
 6755        indoc! {"
 6756            // ˇThis is a long comment that will be wrapped.
 6757            //
 6758            // And this is another long comment that will also be wrapped.ˇ
 6759         "},
 6760        indoc! {"
 6761            // ˇThis is a long comment that will be
 6762            // wrapped.
 6763            //
 6764            // And this is another long comment that
 6765            // will also be wrapped.ˇ
 6766         "},
 6767        cpp_language,
 6768        &mut cx,
 6769    );
 6770
 6771    #[track_caller]
 6772    fn assert_rewrap(
 6773        unwrapped_text: &str,
 6774        wrapped_text: &str,
 6775        language: Arc<Language>,
 6776        cx: &mut EditorTestContext,
 6777    ) {
 6778        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6779        cx.set_state(unwrapped_text);
 6780        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6781        cx.assert_editor_state(wrapped_text);
 6782    }
 6783}
 6784
 6785#[gpui::test]
 6786async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6787    init_test(cx, |settings| {
 6788        settings.languages.0.extend([(
 6789            "Rust".into(),
 6790            LanguageSettingsContent {
 6791                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6792                preferred_line_length: Some(40),
 6793                ..Default::default()
 6794            },
 6795        )])
 6796    });
 6797
 6798    let mut cx = EditorTestContext::new(cx).await;
 6799
 6800    let rust_lang = Arc::new(
 6801        Language::new(
 6802            LanguageConfig {
 6803                name: "Rust".into(),
 6804                line_comments: vec!["// ".into()],
 6805                block_comment: Some(BlockCommentConfig {
 6806                    start: "/*".into(),
 6807                    end: "*/".into(),
 6808                    prefix: "* ".into(),
 6809                    tab_size: 1,
 6810                }),
 6811                documentation_comment: Some(BlockCommentConfig {
 6812                    start: "/**".into(),
 6813                    end: "*/".into(),
 6814                    prefix: "* ".into(),
 6815                    tab_size: 1,
 6816                }),
 6817
 6818                ..LanguageConfig::default()
 6819            },
 6820            Some(tree_sitter_rust::LANGUAGE.into()),
 6821        )
 6822        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6823        .unwrap(),
 6824    );
 6825
 6826    // regular block comment
 6827    assert_rewrap(
 6828        indoc! {"
 6829            /*
 6830             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6831             */
 6832            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6833        "},
 6834        indoc! {"
 6835            /*
 6836             *ˇ Lorem ipsum dolor sit amet,
 6837             * consectetur adipiscing elit.
 6838             */
 6839            /*
 6840             *ˇ Lorem ipsum dolor sit amet,
 6841             * consectetur adipiscing elit.
 6842             */
 6843        "},
 6844        rust_lang.clone(),
 6845        &mut cx,
 6846    );
 6847
 6848    // indent is respected
 6849    assert_rewrap(
 6850        indoc! {"
 6851            {}
 6852                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6853        "},
 6854        indoc! {"
 6855            {}
 6856                /*
 6857                 *ˇ Lorem ipsum dolor sit amet,
 6858                 * consectetur adipiscing elit.
 6859                 */
 6860        "},
 6861        rust_lang.clone(),
 6862        &mut cx,
 6863    );
 6864
 6865    // short block comments with inline delimiters
 6866    assert_rewrap(
 6867        indoc! {"
 6868            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6869            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6870             */
 6871            /*
 6872             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6873        "},
 6874        indoc! {"
 6875            /*
 6876             *ˇ Lorem ipsum dolor sit amet,
 6877             * consectetur adipiscing elit.
 6878             */
 6879            /*
 6880             *ˇ Lorem ipsum dolor sit amet,
 6881             * consectetur adipiscing elit.
 6882             */
 6883            /*
 6884             *ˇ Lorem ipsum dolor sit amet,
 6885             * consectetur adipiscing elit.
 6886             */
 6887        "},
 6888        rust_lang.clone(),
 6889        &mut cx,
 6890    );
 6891
 6892    // multiline block comment with inline start/end delimiters
 6893    assert_rewrap(
 6894        indoc! {"
 6895            /*ˇ Lorem ipsum dolor sit amet,
 6896             * consectetur adipiscing elit. */
 6897        "},
 6898        indoc! {"
 6899            /*
 6900             *ˇ Lorem ipsum dolor sit amet,
 6901             * consectetur adipiscing elit.
 6902             */
 6903        "},
 6904        rust_lang.clone(),
 6905        &mut cx,
 6906    );
 6907
 6908    // block comment rewrap still respects paragraph bounds
 6909    assert_rewrap(
 6910        indoc! {"
 6911            /*
 6912             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6913             *
 6914             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6915             */
 6916        "},
 6917        indoc! {"
 6918            /*
 6919             *ˇ Lorem ipsum dolor sit amet,
 6920             * consectetur adipiscing elit.
 6921             *
 6922             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6923             */
 6924        "},
 6925        rust_lang.clone(),
 6926        &mut cx,
 6927    );
 6928
 6929    // documentation comments
 6930    assert_rewrap(
 6931        indoc! {"
 6932            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6933            /**
 6934             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6935             */
 6936        "},
 6937        indoc! {"
 6938            /**
 6939             *ˇ Lorem ipsum dolor sit amet,
 6940             * consectetur adipiscing elit.
 6941             */
 6942            /**
 6943             *ˇ Lorem ipsum dolor sit amet,
 6944             * consectetur adipiscing elit.
 6945             */
 6946        "},
 6947        rust_lang.clone(),
 6948        &mut cx,
 6949    );
 6950
 6951    // different, adjacent comments
 6952    assert_rewrap(
 6953        indoc! {"
 6954            /**
 6955             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6956             */
 6957            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6958            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6959        "},
 6960        indoc! {"
 6961            /**
 6962             *ˇ Lorem ipsum dolor sit amet,
 6963             * consectetur adipiscing elit.
 6964             */
 6965            /*
 6966             *ˇ Lorem ipsum dolor sit amet,
 6967             * consectetur adipiscing elit.
 6968             */
 6969            //ˇ Lorem ipsum dolor sit amet,
 6970            // consectetur adipiscing elit.
 6971        "},
 6972        rust_lang.clone(),
 6973        &mut cx,
 6974    );
 6975
 6976    // selection w/ single short block comment
 6977    assert_rewrap(
 6978        indoc! {"
 6979            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6980        "},
 6981        indoc! {"
 6982            «/*
 6983             * Lorem ipsum dolor sit amet,
 6984             * consectetur adipiscing elit.
 6985             */ˇ»
 6986        "},
 6987        rust_lang.clone(),
 6988        &mut cx,
 6989    );
 6990
 6991    // rewrapping a single comment w/ abutting comments
 6992    assert_rewrap(
 6993        indoc! {"
 6994            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6995            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6996        "},
 6997        indoc! {"
 6998            /*
 6999             * ˇLorem ipsum dolor sit amet,
 7000             * consectetur adipiscing elit.
 7001             */
 7002            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7003        "},
 7004        rust_lang.clone(),
 7005        &mut cx,
 7006    );
 7007
 7008    // selection w/ non-abutting short block comments
 7009    assert_rewrap(
 7010        indoc! {"
 7011            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7012
 7013            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7014        "},
 7015        indoc! {"
 7016            «/*
 7017             * Lorem ipsum dolor sit amet,
 7018             * consectetur adipiscing elit.
 7019             */
 7020
 7021            /*
 7022             * Lorem ipsum dolor sit amet,
 7023             * consectetur adipiscing elit.
 7024             */ˇ»
 7025        "},
 7026        rust_lang.clone(),
 7027        &mut cx,
 7028    );
 7029
 7030    // selection of multiline block comments
 7031    assert_rewrap(
 7032        indoc! {"
 7033            «/* Lorem ipsum dolor sit amet,
 7034             * consectetur adipiscing elit. */ˇ»
 7035        "},
 7036        indoc! {"
 7037            «/*
 7038             * Lorem ipsum dolor sit amet,
 7039             * consectetur adipiscing elit.
 7040             */ˇ»
 7041        "},
 7042        rust_lang.clone(),
 7043        &mut cx,
 7044    );
 7045
 7046    // partial selection of multiline block comments
 7047    assert_rewrap(
 7048        indoc! {"
 7049            «/* Lorem ipsum dolor sit amet,ˇ»
 7050             * consectetur adipiscing elit. */
 7051            /* Lorem ipsum dolor sit amet,
 7052             «* consectetur adipiscing elit. */ˇ»
 7053        "},
 7054        indoc! {"
 7055            «/*
 7056             * Lorem ipsum dolor sit amet,ˇ»
 7057             * consectetur adipiscing elit. */
 7058            /* Lorem ipsum dolor sit amet,
 7059             «* consectetur adipiscing elit.
 7060             */ˇ»
 7061        "},
 7062        rust_lang.clone(),
 7063        &mut cx,
 7064    );
 7065
 7066    // selection w/ abutting short block comments
 7067    // TODO: should not be combined; should rewrap as 2 comments
 7068    assert_rewrap(
 7069        indoc! {"
 7070            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7071            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7072        "},
 7073        // desired behavior:
 7074        // indoc! {"
 7075        //     «/*
 7076        //      * Lorem ipsum dolor sit amet,
 7077        //      * consectetur adipiscing elit.
 7078        //      */
 7079        //     /*
 7080        //      * Lorem ipsum dolor sit amet,
 7081        //      * consectetur adipiscing elit.
 7082        //      */ˇ»
 7083        // "},
 7084        // actual behaviour:
 7085        indoc! {"
 7086            «/*
 7087             * Lorem ipsum dolor sit amet,
 7088             * consectetur adipiscing elit. Lorem
 7089             * ipsum dolor sit amet, consectetur
 7090             * adipiscing elit.
 7091             */ˇ»
 7092        "},
 7093        rust_lang.clone(),
 7094        &mut cx,
 7095    );
 7096
 7097    // TODO: same as above, but with delimiters on separate line
 7098    // assert_rewrap(
 7099    //     indoc! {"
 7100    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7101    //          */
 7102    //         /*
 7103    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7104    //     "},
 7105    //     // desired:
 7106    //     // indoc! {"
 7107    //     //     «/*
 7108    //     //      * Lorem ipsum dolor sit amet,
 7109    //     //      * consectetur adipiscing elit.
 7110    //     //      */
 7111    //     //     /*
 7112    //     //      * Lorem ipsum dolor sit amet,
 7113    //     //      * consectetur adipiscing elit.
 7114    //     //      */ˇ»
 7115    //     // "},
 7116    //     // actual: (but with trailing w/s on the empty lines)
 7117    //     indoc! {"
 7118    //         «/*
 7119    //          * Lorem ipsum dolor sit amet,
 7120    //          * consectetur adipiscing elit.
 7121    //          *
 7122    //          */
 7123    //         /*
 7124    //          *
 7125    //          * Lorem ipsum dolor sit amet,
 7126    //          * consectetur adipiscing elit.
 7127    //          */ˇ»
 7128    //     "},
 7129    //     rust_lang.clone(),
 7130    //     &mut cx,
 7131    // );
 7132
 7133    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7134    assert_rewrap(
 7135        indoc! {"
 7136            /*
 7137             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7138             */
 7139            /*
 7140             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7141            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7142        "},
 7143        // desired:
 7144        // indoc! {"
 7145        //     /*
 7146        //      *ˇ Lorem ipsum dolor sit amet,
 7147        //      * consectetur adipiscing elit.
 7148        //      */
 7149        //     /*
 7150        //      *ˇ Lorem ipsum dolor sit amet,
 7151        //      * consectetur adipiscing elit.
 7152        //      */
 7153        //     /*
 7154        //      *ˇ Lorem ipsum dolor sit amet
 7155        //      */ /* consectetur adipiscing elit. */
 7156        // "},
 7157        // actual:
 7158        indoc! {"
 7159            /*
 7160             //ˇ Lorem ipsum dolor sit amet,
 7161             // consectetur adipiscing elit.
 7162             */
 7163            /*
 7164             * //ˇ Lorem ipsum dolor sit amet,
 7165             * consectetur adipiscing elit.
 7166             */
 7167            /*
 7168             *ˇ Lorem ipsum dolor sit amet */ /*
 7169             * consectetur adipiscing elit.
 7170             */
 7171        "},
 7172        rust_lang,
 7173        &mut cx,
 7174    );
 7175
 7176    #[track_caller]
 7177    fn assert_rewrap(
 7178        unwrapped_text: &str,
 7179        wrapped_text: &str,
 7180        language: Arc<Language>,
 7181        cx: &mut EditorTestContext,
 7182    ) {
 7183        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7184        cx.set_state(unwrapped_text);
 7185        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7186        cx.assert_editor_state(wrapped_text);
 7187    }
 7188}
 7189
 7190#[gpui::test]
 7191async fn test_hard_wrap(cx: &mut TestAppContext) {
 7192    init_test(cx, |_| {});
 7193    let mut cx = EditorTestContext::new(cx).await;
 7194
 7195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7196    cx.update_editor(|editor, _, cx| {
 7197        editor.set_hard_wrap(Some(14), cx);
 7198    });
 7199
 7200    cx.set_state(indoc!(
 7201        "
 7202        one two three ˇ
 7203        "
 7204    ));
 7205    cx.simulate_input("four");
 7206    cx.run_until_parked();
 7207
 7208    cx.assert_editor_state(indoc!(
 7209        "
 7210        one two three
 7211        fourˇ
 7212        "
 7213    ));
 7214
 7215    cx.update_editor(|editor, window, cx| {
 7216        editor.newline(&Default::default(), window, cx);
 7217    });
 7218    cx.run_until_parked();
 7219    cx.assert_editor_state(indoc!(
 7220        "
 7221        one two three
 7222        four
 7223        ˇ
 7224        "
 7225    ));
 7226
 7227    cx.simulate_input("five");
 7228    cx.run_until_parked();
 7229    cx.assert_editor_state(indoc!(
 7230        "
 7231        one two three
 7232        four
 7233        fiveˇ
 7234        "
 7235    ));
 7236
 7237    cx.update_editor(|editor, window, cx| {
 7238        editor.newline(&Default::default(), window, cx);
 7239    });
 7240    cx.run_until_parked();
 7241    cx.simulate_input("# ");
 7242    cx.run_until_parked();
 7243    cx.assert_editor_state(indoc!(
 7244        "
 7245        one two three
 7246        four
 7247        five
 7248        # ˇ
 7249        "
 7250    ));
 7251
 7252    cx.update_editor(|editor, window, cx| {
 7253        editor.newline(&Default::default(), window, cx);
 7254    });
 7255    cx.run_until_parked();
 7256    cx.assert_editor_state(indoc!(
 7257        "
 7258        one two three
 7259        four
 7260        five
 7261        #\x20
 7262 7263        "
 7264    ));
 7265
 7266    cx.simulate_input(" 6");
 7267    cx.run_until_parked();
 7268    cx.assert_editor_state(indoc!(
 7269        "
 7270        one two three
 7271        four
 7272        five
 7273        #
 7274        # 6ˇ
 7275        "
 7276    ));
 7277}
 7278
 7279#[gpui::test]
 7280async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7281    init_test(cx, |_| {});
 7282
 7283    let mut cx = EditorTestContext::new(cx).await;
 7284
 7285    cx.set_state(indoc! {"The quick brownˇ"});
 7286    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7287    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7288
 7289    cx.set_state(indoc! {"The emacs foxˇ"});
 7290    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7291    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7292
 7293    cx.set_state(indoc! {"
 7294        The quick« brownˇ»
 7295        fox jumps overˇ
 7296        the lazy dog"});
 7297    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7298    cx.assert_editor_state(indoc! {"
 7299        The quickˇ
 7300        ˇthe lazy dog"});
 7301
 7302    cx.set_state(indoc! {"
 7303        The quick« brownˇ»
 7304        fox jumps overˇ
 7305        the lazy dog"});
 7306    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7307    cx.assert_editor_state(indoc! {"
 7308        The quickˇ
 7309        fox jumps overˇthe lazy dog"});
 7310
 7311    cx.set_state(indoc! {"
 7312        The quick« brownˇ»
 7313        fox jumps overˇ
 7314        the lazy dog"});
 7315    cx.update_editor(|e, window, cx| {
 7316        e.cut_to_end_of_line(
 7317            &CutToEndOfLine {
 7318                stop_at_newlines: true,
 7319            },
 7320            window,
 7321            cx,
 7322        )
 7323    });
 7324    cx.assert_editor_state(indoc! {"
 7325        The quickˇ
 7326        fox jumps overˇ
 7327        the lazy dog"});
 7328
 7329    cx.set_state(indoc! {"
 7330        The quick« brownˇ»
 7331        fox jumps overˇ
 7332        the lazy dog"});
 7333    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7334    cx.assert_editor_state(indoc! {"
 7335        The quickˇ
 7336        fox jumps overˇthe lazy dog"});
 7337}
 7338
 7339#[gpui::test]
 7340async fn test_clipboard(cx: &mut TestAppContext) {
 7341    init_test(cx, |_| {});
 7342
 7343    let mut cx = EditorTestContext::new(cx).await;
 7344
 7345    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7346    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7347    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7348
 7349    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7350    cx.set_state("two ˇfour ˇsix ˇ");
 7351    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7352    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7353
 7354    // Paste again but with only two cursors. Since the number of cursors doesn't
 7355    // match the number of slices in the clipboard, the entire clipboard text
 7356    // is pasted at each cursor.
 7357    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7358    cx.update_editor(|e, window, cx| {
 7359        e.handle_input("( ", window, cx);
 7360        e.paste(&Paste, window, cx);
 7361        e.handle_input(") ", window, cx);
 7362    });
 7363    cx.assert_editor_state(
 7364        &([
 7365            "( one✅ ",
 7366            "three ",
 7367            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7368            "three ",
 7369            "five ) ˇ",
 7370        ]
 7371        .join("\n")),
 7372    );
 7373
 7374    // Cut with three selections, one of which is full-line.
 7375    cx.set_state(indoc! {"
 7376        1«2ˇ»3
 7377        4ˇ567
 7378        «8ˇ»9"});
 7379    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7380    cx.assert_editor_state(indoc! {"
 7381        1ˇ3
 7382        ˇ9"});
 7383
 7384    // Paste with three selections, noticing how the copied selection that was full-line
 7385    // gets inserted before the second cursor.
 7386    cx.set_state(indoc! {"
 7387        1ˇ3
 7388 7389        «oˇ»ne"});
 7390    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7391    cx.assert_editor_state(indoc! {"
 7392        12ˇ3
 7393        4567
 7394 7395        8ˇne"});
 7396
 7397    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7398    cx.set_state(indoc! {"
 7399        The quick brown
 7400        fox juˇmps over
 7401        the lazy dog"});
 7402    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7403    assert_eq!(
 7404        cx.read_from_clipboard()
 7405            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7406        Some("fox jumps over\n".to_string())
 7407    );
 7408
 7409    // Paste with three selections, noticing how the copied full-line selection is inserted
 7410    // before the empty selections but replaces the selection that is non-empty.
 7411    cx.set_state(indoc! {"
 7412        Tˇhe quick brown
 7413        «foˇ»x jumps over
 7414        tˇhe lazy dog"});
 7415    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7416    cx.assert_editor_state(indoc! {"
 7417        fox jumps over
 7418        Tˇhe quick brown
 7419        fox jumps over
 7420        ˇx jumps over
 7421        fox jumps over
 7422        tˇhe lazy dog"});
 7423}
 7424
 7425#[gpui::test]
 7426async fn test_copy_trim(cx: &mut TestAppContext) {
 7427    init_test(cx, |_| {});
 7428
 7429    let mut cx = EditorTestContext::new(cx).await;
 7430    cx.set_state(
 7431        r#"            «for selection in selections.iter() {
 7432            let mut start = selection.start;
 7433            let mut end = selection.end;
 7434            let is_entire_line = selection.is_empty();
 7435            if is_entire_line {
 7436                start = Point::new(start.row, 0);ˇ»
 7437                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7438            }
 7439        "#,
 7440    );
 7441    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7442    assert_eq!(
 7443        cx.read_from_clipboard()
 7444            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7445        Some(
 7446            "for selection in selections.iter() {
 7447            let mut start = selection.start;
 7448            let mut end = selection.end;
 7449            let is_entire_line = selection.is_empty();
 7450            if is_entire_line {
 7451                start = Point::new(start.row, 0);"
 7452                .to_string()
 7453        ),
 7454        "Regular copying preserves all indentation selected",
 7455    );
 7456    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7457    assert_eq!(
 7458        cx.read_from_clipboard()
 7459            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7460        Some(
 7461            "for selection in selections.iter() {
 7462let mut start = selection.start;
 7463let mut end = selection.end;
 7464let is_entire_line = selection.is_empty();
 7465if is_entire_line {
 7466    start = Point::new(start.row, 0);"
 7467                .to_string()
 7468        ),
 7469        "Copying with stripping should strip all leading whitespaces"
 7470    );
 7471
 7472    cx.set_state(
 7473        r#"       «     for selection in selections.iter() {
 7474            let mut start = selection.start;
 7475            let mut end = selection.end;
 7476            let is_entire_line = selection.is_empty();
 7477            if is_entire_line {
 7478                start = Point::new(start.row, 0);ˇ»
 7479                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7480            }
 7481        "#,
 7482    );
 7483    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7484    assert_eq!(
 7485        cx.read_from_clipboard()
 7486            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7487        Some(
 7488            "     for selection in selections.iter() {
 7489            let mut start = selection.start;
 7490            let mut end = selection.end;
 7491            let is_entire_line = selection.is_empty();
 7492            if is_entire_line {
 7493                start = Point::new(start.row, 0);"
 7494                .to_string()
 7495        ),
 7496        "Regular copying preserves all indentation selected",
 7497    );
 7498    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7499    assert_eq!(
 7500        cx.read_from_clipboard()
 7501            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7502        Some(
 7503            "for selection in selections.iter() {
 7504let mut start = selection.start;
 7505let mut end = selection.end;
 7506let is_entire_line = selection.is_empty();
 7507if is_entire_line {
 7508    start = Point::new(start.row, 0);"
 7509                .to_string()
 7510        ),
 7511        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7512    );
 7513
 7514    cx.set_state(
 7515        r#"       «ˇ     for selection in selections.iter() {
 7516            let mut start = selection.start;
 7517            let mut end = selection.end;
 7518            let is_entire_line = selection.is_empty();
 7519            if is_entire_line {
 7520                start = Point::new(start.row, 0);»
 7521                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7522            }
 7523        "#,
 7524    );
 7525    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7526    assert_eq!(
 7527        cx.read_from_clipboard()
 7528            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7529        Some(
 7530            "     for selection in selections.iter() {
 7531            let mut start = selection.start;
 7532            let mut end = selection.end;
 7533            let is_entire_line = selection.is_empty();
 7534            if is_entire_line {
 7535                start = Point::new(start.row, 0);"
 7536                .to_string()
 7537        ),
 7538        "Regular copying for reverse selection works the same",
 7539    );
 7540    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7541    assert_eq!(
 7542        cx.read_from_clipboard()
 7543            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7544        Some(
 7545            "for selection in selections.iter() {
 7546let mut start = selection.start;
 7547let mut end = selection.end;
 7548let is_entire_line = selection.is_empty();
 7549if is_entire_line {
 7550    start = Point::new(start.row, 0);"
 7551                .to_string()
 7552        ),
 7553        "Copying with stripping for reverse selection works the same"
 7554    );
 7555
 7556    cx.set_state(
 7557        r#"            for selection «in selections.iter() {
 7558            let mut start = selection.start;
 7559            let mut end = selection.end;
 7560            let is_entire_line = selection.is_empty();
 7561            if is_entire_line {
 7562                start = Point::new(start.row, 0);ˇ»
 7563                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7564            }
 7565        "#,
 7566    );
 7567    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7568    assert_eq!(
 7569        cx.read_from_clipboard()
 7570            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7571        Some(
 7572            "in selections.iter() {
 7573            let mut start = selection.start;
 7574            let mut end = selection.end;
 7575            let is_entire_line = selection.is_empty();
 7576            if is_entire_line {
 7577                start = Point::new(start.row, 0);"
 7578                .to_string()
 7579        ),
 7580        "When selecting past the indent, the copying works as usual",
 7581    );
 7582    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7583    assert_eq!(
 7584        cx.read_from_clipboard()
 7585            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7586        Some(
 7587            "in selections.iter() {
 7588            let mut start = selection.start;
 7589            let mut end = selection.end;
 7590            let is_entire_line = selection.is_empty();
 7591            if is_entire_line {
 7592                start = Point::new(start.row, 0);"
 7593                .to_string()
 7594        ),
 7595        "When selecting past the indent, nothing is trimmed"
 7596    );
 7597
 7598    cx.set_state(
 7599        r#"            «for selection in selections.iter() {
 7600            let mut start = selection.start;
 7601
 7602            let mut end = selection.end;
 7603            let is_entire_line = selection.is_empty();
 7604            if is_entire_line {
 7605                start = Point::new(start.row, 0);
 7606ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7607            }
 7608        "#,
 7609    );
 7610    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7611    assert_eq!(
 7612        cx.read_from_clipboard()
 7613            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7614        Some(
 7615            "for selection in selections.iter() {
 7616let mut start = selection.start;
 7617
 7618let mut end = selection.end;
 7619let is_entire_line = selection.is_empty();
 7620if is_entire_line {
 7621    start = Point::new(start.row, 0);
 7622"
 7623            .to_string()
 7624        ),
 7625        "Copying with stripping should ignore empty lines"
 7626    );
 7627}
 7628
 7629#[gpui::test]
 7630async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 7631    init_test(cx, |_| {});
 7632
 7633    let mut cx = EditorTestContext::new(cx).await;
 7634
 7635    cx.set_state(indoc! {"
 7636        «    a
 7637            bˇ»
 7638    "});
 7639    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 7640    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 7641
 7642    assert_eq!(
 7643        cx.read_from_clipboard().and_then(|item| item.text()),
 7644        Some("a\nb\n".to_string())
 7645    );
 7646}
 7647
 7648#[gpui::test]
 7649async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 7650    init_test(cx, |_| {});
 7651
 7652    let fs = FakeFs::new(cx.executor());
 7653    fs.insert_file(
 7654        path!("/file.txt"),
 7655        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 7656    )
 7657    .await;
 7658
 7659    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 7660
 7661    let buffer = project
 7662        .update(cx, |project, cx| {
 7663            project.open_local_buffer(path!("/file.txt"), cx)
 7664        })
 7665        .await
 7666        .unwrap();
 7667
 7668    let multibuffer = cx.new(|cx| {
 7669        let mut multibuffer = MultiBuffer::new(ReadWrite);
 7670        multibuffer.push_excerpts(
 7671            buffer.clone(),
 7672            [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))],
 7673            cx,
 7674        );
 7675        multibuffer
 7676    });
 7677
 7678    let (editor, cx) = cx.add_window_view(|window, cx| {
 7679        build_editor_with_project(project.clone(), multibuffer, window, cx)
 7680    });
 7681
 7682    editor.update_in(cx, |editor, window, cx| {
 7683        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 7684
 7685        editor.select_all(&SelectAll, window, cx);
 7686        editor.copy(&Copy, window, cx);
 7687    });
 7688
 7689    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 7690        .read_from_clipboard()
 7691        .and_then(|item| item.entries().first().cloned())
 7692        .and_then(|entry| match entry {
 7693            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 7694            _ => None,
 7695        });
 7696
 7697    let selections = clipboard_selections.expect("should have clipboard selections");
 7698    assert_eq!(selections.len(), 1);
 7699    let selection = &selections[0];
 7700    assert_eq!(
 7701        selection.line_range,
 7702        Some(2..=5),
 7703        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 7704    );
 7705}
 7706
 7707#[gpui::test]
 7708async fn test_paste_multiline(cx: &mut TestAppContext) {
 7709    init_test(cx, |_| {});
 7710
 7711    let mut cx = EditorTestContext::new(cx).await;
 7712    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7713
 7714    // Cut an indented block, without the leading whitespace.
 7715    cx.set_state(indoc! {"
 7716        const a: B = (
 7717            c(),
 7718            «d(
 7719                e,
 7720                f
 7721            )ˇ»
 7722        );
 7723    "});
 7724    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7725    cx.assert_editor_state(indoc! {"
 7726        const a: B = (
 7727            c(),
 7728            ˇ
 7729        );
 7730    "});
 7731
 7732    // Paste it at the same position.
 7733    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7734    cx.assert_editor_state(indoc! {"
 7735        const a: B = (
 7736            c(),
 7737            d(
 7738                e,
 7739                f
 7740 7741        );
 7742    "});
 7743
 7744    // Paste it at a line with a lower indent level.
 7745    cx.set_state(indoc! {"
 7746        ˇ
 7747        const a: B = (
 7748            c(),
 7749        );
 7750    "});
 7751    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7752    cx.assert_editor_state(indoc! {"
 7753        d(
 7754            e,
 7755            f
 7756 7757        const a: B = (
 7758            c(),
 7759        );
 7760    "});
 7761
 7762    // Cut an indented block, with the leading whitespace.
 7763    cx.set_state(indoc! {"
 7764        const a: B = (
 7765            c(),
 7766        «    d(
 7767                e,
 7768                f
 7769            )
 7770        ˇ»);
 7771    "});
 7772    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7773    cx.assert_editor_state(indoc! {"
 7774        const a: B = (
 7775            c(),
 7776        ˇ);
 7777    "});
 7778
 7779    // Paste it at the same position.
 7780    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7781    cx.assert_editor_state(indoc! {"
 7782        const a: B = (
 7783            c(),
 7784            d(
 7785                e,
 7786                f
 7787            )
 7788        ˇ);
 7789    "});
 7790
 7791    // Paste it at a line with a higher indent level.
 7792    cx.set_state(indoc! {"
 7793        const a: B = (
 7794            c(),
 7795            d(
 7796                e,
 7797 7798            )
 7799        );
 7800    "});
 7801    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7802    cx.assert_editor_state(indoc! {"
 7803        const a: B = (
 7804            c(),
 7805            d(
 7806                e,
 7807                f    d(
 7808                    e,
 7809                    f
 7810                )
 7811        ˇ
 7812            )
 7813        );
 7814    "});
 7815
 7816    // Copy an indented block, starting mid-line
 7817    cx.set_state(indoc! {"
 7818        const a: B = (
 7819            c(),
 7820            somethin«g(
 7821                e,
 7822                f
 7823            )ˇ»
 7824        );
 7825    "});
 7826    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7827
 7828    // Paste it on a line with a lower indent level
 7829    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7830    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7831    cx.assert_editor_state(indoc! {"
 7832        const a: B = (
 7833            c(),
 7834            something(
 7835                e,
 7836                f
 7837            )
 7838        );
 7839        g(
 7840            e,
 7841            f
 7842"});
 7843}
 7844
 7845#[gpui::test]
 7846async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7847    init_test(cx, |_| {});
 7848
 7849    cx.write_to_clipboard(ClipboardItem::new_string(
 7850        "    d(\n        e\n    );\n".into(),
 7851    ));
 7852
 7853    let mut cx = EditorTestContext::new(cx).await;
 7854    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7855    cx.run_until_parked();
 7856
 7857    cx.set_state(indoc! {"
 7858        fn a() {
 7859            b();
 7860            if c() {
 7861                ˇ
 7862            }
 7863        }
 7864    "});
 7865
 7866    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7867    cx.assert_editor_state(indoc! {"
 7868        fn a() {
 7869            b();
 7870            if c() {
 7871                d(
 7872                    e
 7873                );
 7874        ˇ
 7875            }
 7876        }
 7877    "});
 7878
 7879    cx.set_state(indoc! {"
 7880        fn a() {
 7881            b();
 7882            ˇ
 7883        }
 7884    "});
 7885
 7886    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7887    cx.assert_editor_state(indoc! {"
 7888        fn a() {
 7889            b();
 7890            d(
 7891                e
 7892            );
 7893        ˇ
 7894        }
 7895    "});
 7896}
 7897
 7898#[gpui::test]
 7899fn test_select_all(cx: &mut TestAppContext) {
 7900    init_test(cx, |_| {});
 7901
 7902    let editor = cx.add_window(|window, cx| {
 7903        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7904        build_editor(buffer, window, cx)
 7905    });
 7906    _ = editor.update(cx, |editor, window, cx| {
 7907        editor.select_all(&SelectAll, window, cx);
 7908        assert_eq!(
 7909            display_ranges(editor, cx),
 7910            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7911        );
 7912    });
 7913}
 7914
 7915#[gpui::test]
 7916fn test_select_line(cx: &mut TestAppContext) {
 7917    init_test(cx, |_| {});
 7918
 7919    let editor = cx.add_window(|window, cx| {
 7920        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7921        build_editor(buffer, window, cx)
 7922    });
 7923    _ = editor.update(cx, |editor, window, cx| {
 7924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7925            s.select_display_ranges([
 7926                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7927                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7928                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7929                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7930            ])
 7931        });
 7932        editor.select_line(&SelectLine, window, cx);
 7933        // Adjacent line selections should NOT merge (only overlapping ones do)
 7934        assert_eq!(
 7935            display_ranges(editor, cx),
 7936            vec![
 7937                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7938                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7939                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7940            ]
 7941        );
 7942    });
 7943
 7944    _ = editor.update(cx, |editor, window, cx| {
 7945        editor.select_line(&SelectLine, window, cx);
 7946        assert_eq!(
 7947            display_ranges(editor, cx),
 7948            vec![
 7949                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7950                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7951            ]
 7952        );
 7953    });
 7954
 7955    _ = editor.update(cx, |editor, window, cx| {
 7956        editor.select_line(&SelectLine, window, cx);
 7957        // Adjacent but not overlapping, so they stay separate
 7958        assert_eq!(
 7959            display_ranges(editor, cx),
 7960            vec![
 7961                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7962                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7963            ]
 7964        );
 7965    });
 7966}
 7967
 7968#[gpui::test]
 7969async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7970    init_test(cx, |_| {});
 7971    let mut cx = EditorTestContext::new(cx).await;
 7972
 7973    #[track_caller]
 7974    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7975        cx.set_state(initial_state);
 7976        cx.update_editor(|e, window, cx| {
 7977            e.split_selection_into_lines(&Default::default(), window, cx)
 7978        });
 7979        cx.assert_editor_state(expected_state);
 7980    }
 7981
 7982    // Selection starts and ends at the middle of lines, left-to-right
 7983    test(
 7984        &mut cx,
 7985        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7986        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7987    );
 7988    // Same thing, right-to-left
 7989    test(
 7990        &mut cx,
 7991        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7992        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7993    );
 7994
 7995    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7996    test(
 7997        &mut cx,
 7998        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7999        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8000    );
 8001    // Same thing, right-to-left
 8002    test(
 8003        &mut cx,
 8004        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 8005        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8006    );
 8007
 8008    // Whole buffer, left-to-right, last line ends with newline
 8009    test(
 8010        &mut cx,
 8011        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 8012        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8013    );
 8014    // Same thing, right-to-left
 8015    test(
 8016        &mut cx,
 8017        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8018        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8019    );
 8020
 8021    // Starts at the end of a line, ends at the start of another
 8022    test(
 8023        &mut cx,
 8024        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8025        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8026    );
 8027}
 8028
 8029#[gpui::test]
 8030async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8031    init_test(cx, |_| {});
 8032
 8033    let editor = cx.add_window(|window, cx| {
 8034        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8035        build_editor(buffer, window, cx)
 8036    });
 8037
 8038    // setup
 8039    _ = editor.update(cx, |editor, window, cx| {
 8040        editor.fold_creases(
 8041            vec![
 8042                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8043                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8044                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8045            ],
 8046            true,
 8047            window,
 8048            cx,
 8049        );
 8050        assert_eq!(
 8051            editor.display_text(cx),
 8052            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8053        );
 8054    });
 8055
 8056    _ = editor.update(cx, |editor, window, cx| {
 8057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8058            s.select_display_ranges([
 8059                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8060                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8061                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8062                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8063            ])
 8064        });
 8065        editor.split_selection_into_lines(&Default::default(), window, cx);
 8066        assert_eq!(
 8067            editor.display_text(cx),
 8068            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8069        );
 8070    });
 8071    EditorTestContext::for_editor(editor, cx)
 8072        .await
 8073        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8074
 8075    _ = editor.update(cx, |editor, window, cx| {
 8076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8077            s.select_display_ranges([
 8078                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8079            ])
 8080        });
 8081        editor.split_selection_into_lines(&Default::default(), window, cx);
 8082        assert_eq!(
 8083            editor.display_text(cx),
 8084            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8085        );
 8086        assert_eq!(
 8087            display_ranges(editor, cx),
 8088            [
 8089                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8090                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8091                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8092                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8093                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8094                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8095                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8096            ]
 8097        );
 8098    });
 8099    EditorTestContext::for_editor(editor, cx)
 8100        .await
 8101        .assert_editor_state(
 8102            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8103        );
 8104}
 8105
 8106#[gpui::test]
 8107async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8108    init_test(cx, |_| {});
 8109
 8110    let mut cx = EditorTestContext::new(cx).await;
 8111
 8112    cx.set_state(indoc!(
 8113        r#"abc
 8114           defˇghi
 8115
 8116           jk
 8117           nlmo
 8118           "#
 8119    ));
 8120
 8121    cx.update_editor(|editor, window, cx| {
 8122        editor.add_selection_above(&Default::default(), window, cx);
 8123    });
 8124
 8125    cx.assert_editor_state(indoc!(
 8126        r#"abcˇ
 8127           defˇghi
 8128
 8129           jk
 8130           nlmo
 8131           "#
 8132    ));
 8133
 8134    cx.update_editor(|editor, window, cx| {
 8135        editor.add_selection_above(&Default::default(), window, cx);
 8136    });
 8137
 8138    cx.assert_editor_state(indoc!(
 8139        r#"abcˇ
 8140            defˇghi
 8141
 8142            jk
 8143            nlmo
 8144            "#
 8145    ));
 8146
 8147    cx.update_editor(|editor, window, cx| {
 8148        editor.add_selection_below(&Default::default(), window, cx);
 8149    });
 8150
 8151    cx.assert_editor_state(indoc!(
 8152        r#"abc
 8153           defˇghi
 8154
 8155           jk
 8156           nlmo
 8157           "#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    cx.assert_editor_state(indoc!(
 8165        r#"abcˇ
 8166           defˇghi
 8167
 8168           jk
 8169           nlmo
 8170           "#
 8171    ));
 8172
 8173    cx.update_editor(|editor, window, cx| {
 8174        editor.redo_selection(&Default::default(), window, cx);
 8175    });
 8176
 8177    cx.assert_editor_state(indoc!(
 8178        r#"abc
 8179           defˇghi
 8180
 8181           jk
 8182           nlmo
 8183           "#
 8184    ));
 8185
 8186    cx.update_editor(|editor, window, cx| {
 8187        editor.add_selection_below(&Default::default(), window, cx);
 8188    });
 8189
 8190    cx.assert_editor_state(indoc!(
 8191        r#"abc
 8192           defˇghi
 8193           ˇ
 8194           jk
 8195           nlmo
 8196           "#
 8197    ));
 8198
 8199    cx.update_editor(|editor, window, cx| {
 8200        editor.add_selection_below(&Default::default(), window, cx);
 8201    });
 8202
 8203    cx.assert_editor_state(indoc!(
 8204        r#"abc
 8205           defˇghi
 8206           ˇ
 8207           jkˇ
 8208           nlmo
 8209           "#
 8210    ));
 8211
 8212    cx.update_editor(|editor, window, cx| {
 8213        editor.add_selection_below(&Default::default(), window, cx);
 8214    });
 8215
 8216    cx.assert_editor_state(indoc!(
 8217        r#"abc
 8218           defˇghi
 8219           ˇ
 8220           jkˇ
 8221           nlmˇo
 8222           "#
 8223    ));
 8224
 8225    cx.update_editor(|editor, window, cx| {
 8226        editor.add_selection_below(&Default::default(), window, cx);
 8227    });
 8228
 8229    cx.assert_editor_state(indoc!(
 8230        r#"abc
 8231           defˇghi
 8232           ˇ
 8233           jkˇ
 8234           nlmˇo
 8235           ˇ"#
 8236    ));
 8237
 8238    // change selections
 8239    cx.set_state(indoc!(
 8240        r#"abc
 8241           def«ˇg»hi
 8242
 8243           jk
 8244           nlmo
 8245           "#
 8246    ));
 8247
 8248    cx.update_editor(|editor, window, cx| {
 8249        editor.add_selection_below(&Default::default(), window, cx);
 8250    });
 8251
 8252    cx.assert_editor_state(indoc!(
 8253        r#"abc
 8254           def«ˇg»hi
 8255
 8256           jk
 8257           nlm«ˇo»
 8258           "#
 8259    ));
 8260
 8261    cx.update_editor(|editor, window, cx| {
 8262        editor.add_selection_below(&Default::default(), window, cx);
 8263    });
 8264
 8265    cx.assert_editor_state(indoc!(
 8266        r#"abc
 8267           def«ˇg»hi
 8268
 8269           jk
 8270           nlm«ˇo»
 8271           "#
 8272    ));
 8273
 8274    cx.update_editor(|editor, window, cx| {
 8275        editor.add_selection_above(&Default::default(), window, cx);
 8276    });
 8277
 8278    cx.assert_editor_state(indoc!(
 8279        r#"abc
 8280           def«ˇg»hi
 8281
 8282           jk
 8283           nlmo
 8284           "#
 8285    ));
 8286
 8287    cx.update_editor(|editor, window, cx| {
 8288        editor.add_selection_above(&Default::default(), window, cx);
 8289    });
 8290
 8291    cx.assert_editor_state(indoc!(
 8292        r#"abc
 8293           def«ˇg»hi
 8294
 8295           jk
 8296           nlmo
 8297           "#
 8298    ));
 8299
 8300    // Change selections again
 8301    cx.set_state(indoc!(
 8302        r#"a«bc
 8303           defgˇ»hi
 8304
 8305           jk
 8306           nlmo
 8307           "#
 8308    ));
 8309
 8310    cx.update_editor(|editor, window, cx| {
 8311        editor.add_selection_below(&Default::default(), window, cx);
 8312    });
 8313
 8314    cx.assert_editor_state(indoc!(
 8315        r#"a«bcˇ»
 8316           d«efgˇ»hi
 8317
 8318           j«kˇ»
 8319           nlmo
 8320           "#
 8321    ));
 8322
 8323    cx.update_editor(|editor, window, cx| {
 8324        editor.add_selection_below(&Default::default(), window, cx);
 8325    });
 8326    cx.assert_editor_state(indoc!(
 8327        r#"a«bcˇ»
 8328           d«efgˇ»hi
 8329
 8330           j«kˇ»
 8331           n«lmoˇ»
 8332           "#
 8333    ));
 8334    cx.update_editor(|editor, window, cx| {
 8335        editor.add_selection_above(&Default::default(), window, cx);
 8336    });
 8337
 8338    cx.assert_editor_state(indoc!(
 8339        r#"a«bcˇ»
 8340           d«efgˇ»hi
 8341
 8342           j«kˇ»
 8343           nlmo
 8344           "#
 8345    ));
 8346
 8347    // Change selections again
 8348    cx.set_state(indoc!(
 8349        r#"abc
 8350           d«ˇefghi
 8351
 8352           jk
 8353           nlm»o
 8354           "#
 8355    ));
 8356
 8357    cx.update_editor(|editor, window, cx| {
 8358        editor.add_selection_above(&Default::default(), window, cx);
 8359    });
 8360
 8361    cx.assert_editor_state(indoc!(
 8362        r#"a«ˇbc»
 8363           d«ˇef»ghi
 8364
 8365           j«ˇk»
 8366           n«ˇlm»o
 8367           "#
 8368    ));
 8369
 8370    cx.update_editor(|editor, window, cx| {
 8371        editor.add_selection_below(&Default::default(), window, cx);
 8372    });
 8373
 8374    cx.assert_editor_state(indoc!(
 8375        r#"abc
 8376           d«ˇef»ghi
 8377
 8378           j«ˇk»
 8379           n«ˇlm»o
 8380           "#
 8381    ));
 8382
 8383    // Assert that the oldest selection's goal column is used when adding more
 8384    // selections, not the most recently added selection's actual column.
 8385    cx.set_state(indoc! {"
 8386        foo bar bazˇ
 8387        foo
 8388        foo bar
 8389    "});
 8390
 8391    cx.update_editor(|editor, window, cx| {
 8392        editor.add_selection_below(
 8393            &AddSelectionBelow {
 8394                skip_soft_wrap: true,
 8395            },
 8396            window,
 8397            cx,
 8398        );
 8399    });
 8400
 8401    cx.assert_editor_state(indoc! {"
 8402        foo bar bazˇ
 8403        fooˇ
 8404        foo bar
 8405    "});
 8406
 8407    cx.update_editor(|editor, window, cx| {
 8408        editor.add_selection_below(
 8409            &AddSelectionBelow {
 8410                skip_soft_wrap: true,
 8411            },
 8412            window,
 8413            cx,
 8414        );
 8415    });
 8416
 8417    cx.assert_editor_state(indoc! {"
 8418        foo bar bazˇ
 8419        fooˇ
 8420        foo barˇ
 8421    "});
 8422
 8423    cx.set_state(indoc! {"
 8424        foo bar baz
 8425        foo
 8426        foo barˇ
 8427    "});
 8428
 8429    cx.update_editor(|editor, window, cx| {
 8430        editor.add_selection_above(
 8431            &AddSelectionAbove {
 8432                skip_soft_wrap: true,
 8433            },
 8434            window,
 8435            cx,
 8436        );
 8437    });
 8438
 8439    cx.assert_editor_state(indoc! {"
 8440        foo bar baz
 8441        fooˇ
 8442        foo barˇ
 8443    "});
 8444
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.add_selection_above(
 8447            &AddSelectionAbove {
 8448                skip_soft_wrap: true,
 8449            },
 8450            window,
 8451            cx,
 8452        );
 8453    });
 8454
 8455    cx.assert_editor_state(indoc! {"
 8456        foo barˇ baz
 8457        fooˇ
 8458        foo barˇ
 8459    "});
 8460}
 8461
 8462#[gpui::test]
 8463async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8464    init_test(cx, |_| {});
 8465    let mut cx = EditorTestContext::new(cx).await;
 8466
 8467    cx.set_state(indoc!(
 8468        r#"line onˇe
 8469           liˇne two
 8470           line three
 8471           line four"#
 8472    ));
 8473
 8474    cx.update_editor(|editor, window, cx| {
 8475        editor.add_selection_below(&Default::default(), window, cx);
 8476    });
 8477
 8478    // test multiple cursors expand in the same direction
 8479    cx.assert_editor_state(indoc!(
 8480        r#"line onˇe
 8481           liˇne twˇo
 8482           liˇne three
 8483           line four"#
 8484    ));
 8485
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.add_selection_below(&Default::default(), window, cx);
 8488    });
 8489
 8490    cx.update_editor(|editor, window, cx| {
 8491        editor.add_selection_below(&Default::default(), window, cx);
 8492    });
 8493
 8494    // test multiple cursors expand below overflow
 8495    cx.assert_editor_state(indoc!(
 8496        r#"line onˇe
 8497           liˇne twˇo
 8498           liˇne thˇree
 8499           liˇne foˇur"#
 8500    ));
 8501
 8502    cx.update_editor(|editor, window, cx| {
 8503        editor.add_selection_above(&Default::default(), window, cx);
 8504    });
 8505
 8506    // test multiple cursors retrieves back correctly
 8507    cx.assert_editor_state(indoc!(
 8508        r#"line onˇe
 8509           liˇne twˇo
 8510           liˇne thˇree
 8511           line four"#
 8512    ));
 8513
 8514    cx.update_editor(|editor, window, cx| {
 8515        editor.add_selection_above(&Default::default(), window, cx);
 8516    });
 8517
 8518    cx.update_editor(|editor, window, cx| {
 8519        editor.add_selection_above(&Default::default(), window, cx);
 8520    });
 8521
 8522    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8523    cx.assert_editor_state(indoc!(
 8524        r#"liˇne onˇe
 8525           liˇne two
 8526           line three
 8527           line four"#
 8528    ));
 8529
 8530    cx.update_editor(|editor, window, cx| {
 8531        editor.undo_selection(&Default::default(), window, cx);
 8532    });
 8533
 8534    // test undo
 8535    cx.assert_editor_state(indoc!(
 8536        r#"line onˇe
 8537           liˇne twˇo
 8538           line three
 8539           line four"#
 8540    ));
 8541
 8542    cx.update_editor(|editor, window, cx| {
 8543        editor.redo_selection(&Default::default(), window, cx);
 8544    });
 8545
 8546    // test redo
 8547    cx.assert_editor_state(indoc!(
 8548        r#"liˇne onˇe
 8549           liˇne two
 8550           line three
 8551           line four"#
 8552    ));
 8553
 8554    cx.set_state(indoc!(
 8555        r#"abcd
 8556           ef«ghˇ»
 8557           ijkl
 8558           «mˇ»nop"#
 8559    ));
 8560
 8561    cx.update_editor(|editor, window, cx| {
 8562        editor.add_selection_above(&Default::default(), window, cx);
 8563    });
 8564
 8565    // test multiple selections expand in the same direction
 8566    cx.assert_editor_state(indoc!(
 8567        r#"ab«cdˇ»
 8568           ef«ghˇ»
 8569           «iˇ»jkl
 8570           «mˇ»nop"#
 8571    ));
 8572
 8573    cx.update_editor(|editor, window, cx| {
 8574        editor.add_selection_above(&Default::default(), window, cx);
 8575    });
 8576
 8577    // test multiple selection upward overflow
 8578    cx.assert_editor_state(indoc!(
 8579        r#"ab«cdˇ»
 8580           «eˇ»f«ghˇ»
 8581           «iˇ»jkl
 8582           «mˇ»nop"#
 8583    ));
 8584
 8585    cx.update_editor(|editor, window, cx| {
 8586        editor.add_selection_below(&Default::default(), window, cx);
 8587    });
 8588
 8589    // test multiple selection retrieves back correctly
 8590    cx.assert_editor_state(indoc!(
 8591        r#"abcd
 8592           ef«ghˇ»
 8593           «iˇ»jkl
 8594           «mˇ»nop"#
 8595    ));
 8596
 8597    cx.update_editor(|editor, window, cx| {
 8598        editor.add_selection_below(&Default::default(), window, cx);
 8599    });
 8600
 8601    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8602    cx.assert_editor_state(indoc!(
 8603        r#"abcd
 8604           ef«ghˇ»
 8605           ij«klˇ»
 8606           «mˇ»nop"#
 8607    ));
 8608
 8609    cx.update_editor(|editor, window, cx| {
 8610        editor.undo_selection(&Default::default(), window, cx);
 8611    });
 8612
 8613    // test undo
 8614    cx.assert_editor_state(indoc!(
 8615        r#"abcd
 8616           ef«ghˇ»
 8617           «iˇ»jkl
 8618           «mˇ»nop"#
 8619    ));
 8620
 8621    cx.update_editor(|editor, window, cx| {
 8622        editor.redo_selection(&Default::default(), window, cx);
 8623    });
 8624
 8625    // test redo
 8626    cx.assert_editor_state(indoc!(
 8627        r#"abcd
 8628           ef«ghˇ»
 8629           ij«klˇ»
 8630           «mˇ»nop"#
 8631    ));
 8632}
 8633
 8634#[gpui::test]
 8635async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8636    init_test(cx, |_| {});
 8637    let mut cx = EditorTestContext::new(cx).await;
 8638
 8639    cx.set_state(indoc!(
 8640        r#"line onˇe
 8641           liˇne two
 8642           line three
 8643           line four"#
 8644    ));
 8645
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.add_selection_below(&Default::default(), window, cx);
 8648        editor.add_selection_below(&Default::default(), window, cx);
 8649        editor.add_selection_below(&Default::default(), window, cx);
 8650    });
 8651
 8652    // initial state with two multi cursor groups
 8653    cx.assert_editor_state(indoc!(
 8654        r#"line onˇe
 8655           liˇne twˇo
 8656           liˇne thˇree
 8657           liˇne foˇur"#
 8658    ));
 8659
 8660    // add single cursor in middle - simulate opt click
 8661    cx.update_editor(|editor, window, cx| {
 8662        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8663        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8664        editor.end_selection(window, cx);
 8665    });
 8666
 8667    cx.assert_editor_state(indoc!(
 8668        r#"line onˇe
 8669           liˇne twˇo
 8670           liˇneˇ thˇree
 8671           liˇne foˇur"#
 8672    ));
 8673
 8674    cx.update_editor(|editor, window, cx| {
 8675        editor.add_selection_above(&Default::default(), window, cx);
 8676    });
 8677
 8678    // test new added selection expands above and existing selection shrinks
 8679    cx.assert_editor_state(indoc!(
 8680        r#"line onˇe
 8681           liˇneˇ twˇo
 8682           liˇneˇ thˇree
 8683           line four"#
 8684    ));
 8685
 8686    cx.update_editor(|editor, window, cx| {
 8687        editor.add_selection_above(&Default::default(), window, cx);
 8688    });
 8689
 8690    // test new added selection expands above and existing selection shrinks
 8691    cx.assert_editor_state(indoc!(
 8692        r#"lineˇ onˇe
 8693           liˇneˇ twˇo
 8694           lineˇ three
 8695           line four"#
 8696    ));
 8697
 8698    // intial state with two selection groups
 8699    cx.set_state(indoc!(
 8700        r#"abcd
 8701           ef«ghˇ»
 8702           ijkl
 8703           «mˇ»nop"#
 8704    ));
 8705
 8706    cx.update_editor(|editor, window, cx| {
 8707        editor.add_selection_above(&Default::default(), window, cx);
 8708        editor.add_selection_above(&Default::default(), window, cx);
 8709    });
 8710
 8711    cx.assert_editor_state(indoc!(
 8712        r#"ab«cdˇ»
 8713           «eˇ»f«ghˇ»
 8714           «iˇ»jkl
 8715           «mˇ»nop"#
 8716    ));
 8717
 8718    // add single selection in middle - simulate opt drag
 8719    cx.update_editor(|editor, window, cx| {
 8720        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8721        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8722        editor.update_selection(
 8723            DisplayPoint::new(DisplayRow(2), 4),
 8724            0,
 8725            gpui::Point::<f32>::default(),
 8726            window,
 8727            cx,
 8728        );
 8729        editor.end_selection(window, cx);
 8730    });
 8731
 8732    cx.assert_editor_state(indoc!(
 8733        r#"ab«cdˇ»
 8734           «eˇ»f«ghˇ»
 8735           «iˇ»jk«lˇ»
 8736           «mˇ»nop"#
 8737    ));
 8738
 8739    cx.update_editor(|editor, window, cx| {
 8740        editor.add_selection_below(&Default::default(), window, cx);
 8741    });
 8742
 8743    // test new added selection expands below, others shrinks from above
 8744    cx.assert_editor_state(indoc!(
 8745        r#"abcd
 8746           ef«ghˇ»
 8747           «iˇ»jk«lˇ»
 8748           «mˇ»no«pˇ»"#
 8749    ));
 8750}
 8751
 8752#[gpui::test]
 8753async fn test_select_next(cx: &mut TestAppContext) {
 8754    init_test(cx, |_| {});
 8755    let mut cx = EditorTestContext::new(cx).await;
 8756
 8757    // Enable case sensitive search.
 8758    update_test_editor_settings(&mut cx, |settings| {
 8759        let mut search_settings = SearchSettingsContent::default();
 8760        search_settings.case_sensitive = Some(true);
 8761        settings.search = Some(search_settings);
 8762    });
 8763
 8764    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8765
 8766    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8767        .unwrap();
 8768    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8773
 8774    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8775    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8776
 8777    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8778    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8783
 8784    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8785        .unwrap();
 8786    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8787
 8788    // Test selection direction should be preserved
 8789    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8790
 8791    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8792        .unwrap();
 8793    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8794
 8795    // Test case sensitivity
 8796    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8797    cx.update_editor(|e, window, cx| {
 8798        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8799    });
 8800    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8801
 8802    // Disable case sensitive search.
 8803    update_test_editor_settings(&mut cx, |settings| {
 8804        let mut search_settings = SearchSettingsContent::default();
 8805        search_settings.case_sensitive = Some(false);
 8806        settings.search = Some(search_settings);
 8807    });
 8808
 8809    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8810    cx.update_editor(|e, window, cx| {
 8811        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8812        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8813    });
 8814    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8815}
 8816
 8817#[gpui::test]
 8818async fn test_select_all_matches(cx: &mut TestAppContext) {
 8819    init_test(cx, |_| {});
 8820    let mut cx = EditorTestContext::new(cx).await;
 8821
 8822    // Enable case sensitive search.
 8823    update_test_editor_settings(&mut cx, |settings| {
 8824        let mut search_settings = SearchSettingsContent::default();
 8825        search_settings.case_sensitive = Some(true);
 8826        settings.search = Some(search_settings);
 8827    });
 8828
 8829    // Test caret-only selections
 8830    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8831    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8832        .unwrap();
 8833    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8834
 8835    // Test left-to-right selections
 8836    cx.set_state("abc\n«abcˇ»\nabc");
 8837    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8838        .unwrap();
 8839    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8840
 8841    // Test right-to-left selections
 8842    cx.set_state("abc\n«ˇabc»\nabc");
 8843    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8844        .unwrap();
 8845    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8846
 8847    // Test selecting whitespace with caret selection
 8848    cx.set_state("abc\nˇ   abc\nabc");
 8849    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8850        .unwrap();
 8851    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8852
 8853    // Test selecting whitespace with left-to-right selection
 8854    cx.set_state("abc\n«ˇ  »abc\nabc");
 8855    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8856        .unwrap();
 8857    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8858
 8859    // Test no matches with right-to-left selection
 8860    cx.set_state("abc\n«  ˇ»abc\nabc");
 8861    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8862        .unwrap();
 8863    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8864
 8865    // Test with a single word and clip_at_line_ends=true (#29823)
 8866    cx.set_state("aˇbc");
 8867    cx.update_editor(|e, window, cx| {
 8868        e.set_clip_at_line_ends(true, cx);
 8869        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8870        e.set_clip_at_line_ends(false, cx);
 8871    });
 8872    cx.assert_editor_state("«abcˇ»");
 8873
 8874    // Test case sensitivity
 8875    cx.set_state("fˇoo\nFOO\nFoo");
 8876    cx.update_editor(|e, window, cx| {
 8877        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8878    });
 8879    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8880
 8881    // Disable case sensitive search.
 8882    update_test_editor_settings(&mut cx, |settings| {
 8883        let mut search_settings = SearchSettingsContent::default();
 8884        search_settings.case_sensitive = Some(false);
 8885        settings.search = Some(search_settings);
 8886    });
 8887
 8888    cx.set_state("fˇoo\nFOO\nFoo");
 8889    cx.update_editor(|e, window, cx| {
 8890        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8891    });
 8892    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8893}
 8894
 8895#[gpui::test]
 8896async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8897    init_test(cx, |_| {});
 8898
 8899    let mut cx = EditorTestContext::new(cx).await;
 8900
 8901    let large_body_1 = "\nd".repeat(200);
 8902    let large_body_2 = "\ne".repeat(200);
 8903
 8904    cx.set_state(&format!(
 8905        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8906    ));
 8907    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8908        let scroll_position = editor.scroll_position(cx);
 8909        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8910        scroll_position
 8911    });
 8912
 8913    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8914        .unwrap();
 8915    cx.assert_editor_state(&format!(
 8916        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8917    ));
 8918    let scroll_position_after_selection =
 8919        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8920    assert_eq!(
 8921        initial_scroll_position, scroll_position_after_selection,
 8922        "Scroll position should not change after selecting all matches"
 8923    );
 8924}
 8925
 8926#[gpui::test]
 8927async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8928    init_test(cx, |_| {});
 8929
 8930    let mut cx = EditorLspTestContext::new_rust(
 8931        lsp::ServerCapabilities {
 8932            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8933            ..Default::default()
 8934        },
 8935        cx,
 8936    )
 8937    .await;
 8938
 8939    cx.set_state(indoc! {"
 8940        line 1
 8941        line 2
 8942        linˇe 3
 8943        line 4
 8944        line 5
 8945    "});
 8946
 8947    // Make an edit
 8948    cx.update_editor(|editor, window, cx| {
 8949        editor.handle_input("X", window, cx);
 8950    });
 8951
 8952    // Move cursor to a different position
 8953    cx.update_editor(|editor, window, cx| {
 8954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8955            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8956        });
 8957    });
 8958
 8959    cx.assert_editor_state(indoc! {"
 8960        line 1
 8961        line 2
 8962        linXe 3
 8963        line 4
 8964        liˇne 5
 8965    "});
 8966
 8967    cx.lsp
 8968        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8969            Ok(Some(vec![lsp::TextEdit::new(
 8970                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8971                "PREFIX ".to_string(),
 8972            )]))
 8973        });
 8974
 8975    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8976        .unwrap()
 8977        .await
 8978        .unwrap();
 8979
 8980    cx.assert_editor_state(indoc! {"
 8981        PREFIX line 1
 8982        line 2
 8983        linXe 3
 8984        line 4
 8985        liˇne 5
 8986    "});
 8987
 8988    // Undo formatting
 8989    cx.update_editor(|editor, window, cx| {
 8990        editor.undo(&Default::default(), window, cx);
 8991    });
 8992
 8993    // Verify cursor moved back to position after edit
 8994    cx.assert_editor_state(indoc! {"
 8995        line 1
 8996        line 2
 8997        linXˇe 3
 8998        line 4
 8999        line 5
 9000    "});
 9001}
 9002
 9003#[gpui::test]
 9004async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 9005    init_test(cx, |_| {});
 9006
 9007    let mut cx = EditorTestContext::new(cx).await;
 9008
 9009    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 9010    cx.update_editor(|editor, window, cx| {
 9011        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 9012    });
 9013
 9014    cx.set_state(indoc! {"
 9015        line 1
 9016        line 2
 9017        linˇe 3
 9018        line 4
 9019        line 5
 9020        line 6
 9021        line 7
 9022        line 8
 9023        line 9
 9024        line 10
 9025    "});
 9026
 9027    let snapshot = cx.buffer_snapshot();
 9028    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 9029
 9030    cx.update(|_, cx| {
 9031        provider.update(cx, |provider, _| {
 9032            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 9033                id: None,
 9034                edits: vec![(edit_position..edit_position, "X".into())],
 9035                cursor_position: None,
 9036                edit_preview: None,
 9037            }))
 9038        })
 9039    });
 9040
 9041    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 9042    cx.update_editor(|editor, window, cx| {
 9043        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 9044    });
 9045
 9046    cx.assert_editor_state(indoc! {"
 9047        line 1
 9048        line 2
 9049        lineXˇ 3
 9050        line 4
 9051        line 5
 9052        line 6
 9053        line 7
 9054        line 8
 9055        line 9
 9056        line 10
 9057    "});
 9058
 9059    cx.update_editor(|editor, window, cx| {
 9060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9061            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 9062        });
 9063    });
 9064
 9065    cx.assert_editor_state(indoc! {"
 9066        line 1
 9067        line 2
 9068        lineX 3
 9069        line 4
 9070        line 5
 9071        line 6
 9072        line 7
 9073        line 8
 9074        line 9
 9075        liˇne 10
 9076    "});
 9077
 9078    cx.update_editor(|editor, window, cx| {
 9079        editor.undo(&Default::default(), window, cx);
 9080    });
 9081
 9082    cx.assert_editor_state(indoc! {"
 9083        line 1
 9084        line 2
 9085        lineˇ 3
 9086        line 4
 9087        line 5
 9088        line 6
 9089        line 7
 9090        line 8
 9091        line 9
 9092        line 10
 9093    "});
 9094}
 9095
 9096#[gpui::test]
 9097async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9098    init_test(cx, |_| {});
 9099
 9100    let mut cx = EditorTestContext::new(cx).await;
 9101    cx.set_state(
 9102        r#"let foo = 2;
 9103lˇet foo = 2;
 9104let fooˇ = 2;
 9105let foo = 2;
 9106let foo = ˇ2;"#,
 9107    );
 9108
 9109    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9110        .unwrap();
 9111    cx.assert_editor_state(
 9112        r#"let foo = 2;
 9113«letˇ» foo = 2;
 9114let «fooˇ» = 2;
 9115let foo = 2;
 9116let foo = «2ˇ»;"#,
 9117    );
 9118
 9119    // noop for multiple selections with different contents
 9120    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9121        .unwrap();
 9122    cx.assert_editor_state(
 9123        r#"let foo = 2;
 9124«letˇ» foo = 2;
 9125let «fooˇ» = 2;
 9126let foo = 2;
 9127let foo = «2ˇ»;"#,
 9128    );
 9129
 9130    // Test last selection direction should be preserved
 9131    cx.set_state(
 9132        r#"let foo = 2;
 9133let foo = 2;
 9134let «fooˇ» = 2;
 9135let «ˇfoo» = 2;
 9136let foo = 2;"#,
 9137    );
 9138
 9139    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9140        .unwrap();
 9141    cx.assert_editor_state(
 9142        r#"let foo = 2;
 9143let foo = 2;
 9144let «fooˇ» = 2;
 9145let «ˇfoo» = 2;
 9146let «ˇfoo» = 2;"#,
 9147    );
 9148}
 9149
 9150#[gpui::test]
 9151async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9152    init_test(cx, |_| {});
 9153
 9154    let mut cx =
 9155        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 9156
 9157    cx.assert_editor_state(indoc! {"
 9158        ˇbbb
 9159        ccc
 9160
 9161        bbb
 9162        ccc
 9163        "});
 9164    cx.dispatch_action(SelectPrevious::default());
 9165    cx.assert_editor_state(indoc! {"
 9166                «bbbˇ»
 9167                ccc
 9168
 9169                bbb
 9170                ccc
 9171                "});
 9172    cx.dispatch_action(SelectPrevious::default());
 9173    cx.assert_editor_state(indoc! {"
 9174                «bbbˇ»
 9175                ccc
 9176
 9177                «bbbˇ»
 9178                ccc
 9179                "});
 9180}
 9181
 9182#[gpui::test]
 9183async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9184    init_test(cx, |_| {});
 9185
 9186    let mut cx = EditorTestContext::new(cx).await;
 9187    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9188
 9189    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9190        .unwrap();
 9191    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9192
 9193    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9194        .unwrap();
 9195    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9196
 9197    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9198    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9199
 9200    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9201    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9202
 9203    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9204        .unwrap();
 9205    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9206
 9207    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9208        .unwrap();
 9209    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9210}
 9211
 9212#[gpui::test]
 9213async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9214    init_test(cx, |_| {});
 9215
 9216    let mut cx = EditorTestContext::new(cx).await;
 9217    cx.set_state("");
 9218
 9219    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9220        .unwrap();
 9221    cx.assert_editor_state("«aˇ»");
 9222    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9223        .unwrap();
 9224    cx.assert_editor_state("«aˇ»");
 9225}
 9226
 9227#[gpui::test]
 9228async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9229    init_test(cx, |_| {});
 9230
 9231    let mut cx = EditorTestContext::new(cx).await;
 9232    cx.set_state(
 9233        r#"let foo = 2;
 9234lˇet foo = 2;
 9235let fooˇ = 2;
 9236let foo = 2;
 9237let foo = ˇ2;"#,
 9238    );
 9239
 9240    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9241        .unwrap();
 9242    cx.assert_editor_state(
 9243        r#"let foo = 2;
 9244«letˇ» foo = 2;
 9245let «fooˇ» = 2;
 9246let foo = 2;
 9247let foo = «2ˇ»;"#,
 9248    );
 9249
 9250    // noop for multiple selections with different contents
 9251    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9252        .unwrap();
 9253    cx.assert_editor_state(
 9254        r#"let foo = 2;
 9255«letˇ» foo = 2;
 9256let «fooˇ» = 2;
 9257let foo = 2;
 9258let foo = «2ˇ»;"#,
 9259    );
 9260}
 9261
 9262#[gpui::test]
 9263async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9264    init_test(cx, |_| {});
 9265    let mut cx = EditorTestContext::new(cx).await;
 9266
 9267    // Enable case sensitive search.
 9268    update_test_editor_settings(&mut cx, |settings| {
 9269        let mut search_settings = SearchSettingsContent::default();
 9270        search_settings.case_sensitive = Some(true);
 9271        settings.search = Some(search_settings);
 9272    });
 9273
 9274    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9275
 9276    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9277        .unwrap();
 9278    // selection direction is preserved
 9279    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9280
 9281    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9282        .unwrap();
 9283    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9284
 9285    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9286    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9287
 9288    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9289    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9290
 9291    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9292        .unwrap();
 9293    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9294
 9295    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9296        .unwrap();
 9297    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9298
 9299    // Test case sensitivity
 9300    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9301    cx.update_editor(|e, window, cx| {
 9302        e.select_previous(&SelectPrevious::default(), window, cx)
 9303            .unwrap();
 9304        e.select_previous(&SelectPrevious::default(), window, cx)
 9305            .unwrap();
 9306    });
 9307    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9308
 9309    // Disable case sensitive search.
 9310    update_test_editor_settings(&mut cx, |settings| {
 9311        let mut search_settings = SearchSettingsContent::default();
 9312        search_settings.case_sensitive = Some(false);
 9313        settings.search = Some(search_settings);
 9314    });
 9315
 9316    cx.set_state("foo\nFOO\n«ˇFoo»");
 9317    cx.update_editor(|e, window, cx| {
 9318        e.select_previous(&SelectPrevious::default(), window, cx)
 9319            .unwrap();
 9320        e.select_previous(&SelectPrevious::default(), window, cx)
 9321            .unwrap();
 9322    });
 9323    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9324}
 9325
 9326#[gpui::test]
 9327async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9328    init_test(cx, |_| {});
 9329
 9330    let language = Arc::new(Language::new(
 9331        LanguageConfig::default(),
 9332        Some(tree_sitter_rust::LANGUAGE.into()),
 9333    ));
 9334
 9335    let text = r#"
 9336        use mod1::mod2::{mod3, mod4};
 9337
 9338        fn fn_1(param1: bool, param2: &str) {
 9339            let var1 = "text";
 9340        }
 9341    "#
 9342    .unindent();
 9343
 9344    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9345    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9346    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9347
 9348    editor
 9349        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9350        .await;
 9351
 9352    editor.update_in(cx, |editor, window, cx| {
 9353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9354            s.select_display_ranges([
 9355                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9356                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9357                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9358            ]);
 9359        });
 9360        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9361    });
 9362    editor.update(cx, |editor, cx| {
 9363        assert_text_with_selections(
 9364            editor,
 9365            indoc! {r#"
 9366                use mod1::mod2::{mod3, «mod4ˇ»};
 9367
 9368                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9369                    let var1 = "«ˇtext»";
 9370                }
 9371            "#},
 9372            cx,
 9373        );
 9374    });
 9375
 9376    editor.update_in(cx, |editor, window, cx| {
 9377        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9378    });
 9379    editor.update(cx, |editor, cx| {
 9380        assert_text_with_selections(
 9381            editor,
 9382            indoc! {r#"
 9383                use mod1::mod2::«{mod3, mod4}ˇ»;
 9384
 9385                «ˇfn fn_1(param1: bool, param2: &str) {
 9386                    let var1 = "text";
 9387 9388            "#},
 9389            cx,
 9390        );
 9391    });
 9392
 9393    editor.update_in(cx, |editor, window, cx| {
 9394        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9395    });
 9396    assert_eq!(
 9397        editor.update(cx, |editor, cx| editor
 9398            .selections
 9399            .display_ranges(&editor.display_snapshot(cx))),
 9400        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9401    );
 9402
 9403    // Trying to expand the selected syntax node one more time has no effect.
 9404    editor.update_in(cx, |editor, window, cx| {
 9405        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9406    });
 9407    assert_eq!(
 9408        editor.update(cx, |editor, cx| editor
 9409            .selections
 9410            .display_ranges(&editor.display_snapshot(cx))),
 9411        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9412    );
 9413
 9414    editor.update_in(cx, |editor, window, cx| {
 9415        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9416    });
 9417    editor.update(cx, |editor, cx| {
 9418        assert_text_with_selections(
 9419            editor,
 9420            indoc! {r#"
 9421                use mod1::mod2::«{mod3, mod4}ˇ»;
 9422
 9423                «ˇfn fn_1(param1: bool, param2: &str) {
 9424                    let var1 = "text";
 9425 9426            "#},
 9427            cx,
 9428        );
 9429    });
 9430
 9431    editor.update_in(cx, |editor, window, cx| {
 9432        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9433    });
 9434    editor.update(cx, |editor, cx| {
 9435        assert_text_with_selections(
 9436            editor,
 9437            indoc! {r#"
 9438                use mod1::mod2::{mod3, «mod4ˇ»};
 9439
 9440                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9441                    let var1 = "«ˇtext»";
 9442                }
 9443            "#},
 9444            cx,
 9445        );
 9446    });
 9447
 9448    editor.update_in(cx, |editor, window, cx| {
 9449        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9450    });
 9451    editor.update(cx, |editor, cx| {
 9452        assert_text_with_selections(
 9453            editor,
 9454            indoc! {r#"
 9455                use mod1::mod2::{mod3, moˇd4};
 9456
 9457                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9458                    let var1 = "teˇxt";
 9459                }
 9460            "#},
 9461            cx,
 9462        );
 9463    });
 9464
 9465    // Trying to shrink the selected syntax node one more time has no effect.
 9466    editor.update_in(cx, |editor, window, cx| {
 9467        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9468    });
 9469    editor.update_in(cx, |editor, _, cx| {
 9470        assert_text_with_selections(
 9471            editor,
 9472            indoc! {r#"
 9473                use mod1::mod2::{mod3, moˇd4};
 9474
 9475                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9476                    let var1 = "teˇxt";
 9477                }
 9478            "#},
 9479            cx,
 9480        );
 9481    });
 9482
 9483    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9484    // a fold.
 9485    editor.update_in(cx, |editor, window, cx| {
 9486        editor.fold_creases(
 9487            vec![
 9488                Crease::simple(
 9489                    Point::new(0, 21)..Point::new(0, 24),
 9490                    FoldPlaceholder::test(),
 9491                ),
 9492                Crease::simple(
 9493                    Point::new(3, 20)..Point::new(3, 22),
 9494                    FoldPlaceholder::test(),
 9495                ),
 9496            ],
 9497            true,
 9498            window,
 9499            cx,
 9500        );
 9501        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9502    });
 9503    editor.update(cx, |editor, cx| {
 9504        assert_text_with_selections(
 9505            editor,
 9506            indoc! {r#"
 9507                use mod1::mod2::«{mod3, mod4}ˇ»;
 9508
 9509                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9510                    let var1 = "«ˇtext»";
 9511                }
 9512            "#},
 9513            cx,
 9514        );
 9515    });
 9516}
 9517
 9518#[gpui::test]
 9519async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9520    init_test(cx, |_| {});
 9521
 9522    let language = Arc::new(Language::new(
 9523        LanguageConfig::default(),
 9524        Some(tree_sitter_rust::LANGUAGE.into()),
 9525    ));
 9526
 9527    let text = "let a = 2;";
 9528
 9529    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9530    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9531    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9532
 9533    editor
 9534        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9535        .await;
 9536
 9537    // Test case 1: Cursor at end of word
 9538    editor.update_in(cx, |editor, window, cx| {
 9539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9540            s.select_display_ranges([
 9541                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9542            ]);
 9543        });
 9544    });
 9545    editor.update(cx, |editor, cx| {
 9546        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9547    });
 9548    editor.update_in(cx, |editor, window, cx| {
 9549        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9550    });
 9551    editor.update(cx, |editor, cx| {
 9552        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9553    });
 9554    editor.update_in(cx, |editor, window, cx| {
 9555        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9556    });
 9557    editor.update(cx, |editor, cx| {
 9558        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9559    });
 9560
 9561    // Test case 2: Cursor at end of statement
 9562    editor.update_in(cx, |editor, window, cx| {
 9563        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9564            s.select_display_ranges([
 9565                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9566            ]);
 9567        });
 9568    });
 9569    editor.update(cx, |editor, cx| {
 9570        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9571    });
 9572    editor.update_in(cx, |editor, window, cx| {
 9573        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9574    });
 9575    editor.update(cx, |editor, cx| {
 9576        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9577    });
 9578}
 9579
 9580#[gpui::test]
 9581async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9582    init_test(cx, |_| {});
 9583
 9584    let language = Arc::new(Language::new(
 9585        LanguageConfig {
 9586            name: "JavaScript".into(),
 9587            ..Default::default()
 9588        },
 9589        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9590    ));
 9591
 9592    let text = r#"
 9593        let a = {
 9594            key: "value",
 9595        };
 9596    "#
 9597    .unindent();
 9598
 9599    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9600    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9601    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9602
 9603    editor
 9604        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9605        .await;
 9606
 9607    // Test case 1: Cursor after '{'
 9608    editor.update_in(cx, |editor, window, cx| {
 9609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9610            s.select_display_ranges([
 9611                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9612            ]);
 9613        });
 9614    });
 9615    editor.update(cx, |editor, cx| {
 9616        assert_text_with_selections(
 9617            editor,
 9618            indoc! {r#"
 9619                let a = {ˇ
 9620                    key: "value",
 9621                };
 9622            "#},
 9623            cx,
 9624        );
 9625    });
 9626    editor.update_in(cx, |editor, window, cx| {
 9627        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9628    });
 9629    editor.update(cx, |editor, cx| {
 9630        assert_text_with_selections(
 9631            editor,
 9632            indoc! {r#"
 9633                let a = «ˇ{
 9634                    key: "value",
 9635                }»;
 9636            "#},
 9637            cx,
 9638        );
 9639    });
 9640
 9641    // Test case 2: Cursor after ':'
 9642    editor.update_in(cx, |editor, window, cx| {
 9643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9644            s.select_display_ranges([
 9645                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9646            ]);
 9647        });
 9648    });
 9649    editor.update(cx, |editor, cx| {
 9650        assert_text_with_selections(
 9651            editor,
 9652            indoc! {r#"
 9653                let a = {
 9654                    key:ˇ "value",
 9655                };
 9656            "#},
 9657            cx,
 9658        );
 9659    });
 9660    editor.update_in(cx, |editor, window, cx| {
 9661        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9662    });
 9663    editor.update(cx, |editor, cx| {
 9664        assert_text_with_selections(
 9665            editor,
 9666            indoc! {r#"
 9667                let a = {
 9668                    «ˇkey: "value"»,
 9669                };
 9670            "#},
 9671            cx,
 9672        );
 9673    });
 9674    editor.update_in(cx, |editor, window, cx| {
 9675        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9676    });
 9677    editor.update(cx, |editor, cx| {
 9678        assert_text_with_selections(
 9679            editor,
 9680            indoc! {r#"
 9681                let a = «ˇ{
 9682                    key: "value",
 9683                }»;
 9684            "#},
 9685            cx,
 9686        );
 9687    });
 9688
 9689    // Test case 3: Cursor after ','
 9690    editor.update_in(cx, |editor, window, cx| {
 9691        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9692            s.select_display_ranges([
 9693                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9694            ]);
 9695        });
 9696    });
 9697    editor.update(cx, |editor, cx| {
 9698        assert_text_with_selections(
 9699            editor,
 9700            indoc! {r#"
 9701                let a = {
 9702                    key: "value",ˇ
 9703                };
 9704            "#},
 9705            cx,
 9706        );
 9707    });
 9708    editor.update_in(cx, |editor, window, cx| {
 9709        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9710    });
 9711    editor.update(cx, |editor, cx| {
 9712        assert_text_with_selections(
 9713            editor,
 9714            indoc! {r#"
 9715                let a = «ˇ{
 9716                    key: "value",
 9717                }»;
 9718            "#},
 9719            cx,
 9720        );
 9721    });
 9722
 9723    // Test case 4: Cursor after ';'
 9724    editor.update_in(cx, |editor, window, cx| {
 9725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9726            s.select_display_ranges([
 9727                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9728            ]);
 9729        });
 9730    });
 9731    editor.update(cx, |editor, cx| {
 9732        assert_text_with_selections(
 9733            editor,
 9734            indoc! {r#"
 9735                let a = {
 9736                    key: "value",
 9737                };ˇ
 9738            "#},
 9739            cx,
 9740        );
 9741    });
 9742    editor.update_in(cx, |editor, window, cx| {
 9743        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9744    });
 9745    editor.update(cx, |editor, cx| {
 9746        assert_text_with_selections(
 9747            editor,
 9748            indoc! {r#"
 9749                «ˇlet a = {
 9750                    key: "value",
 9751                };
 9752                »"#},
 9753            cx,
 9754        );
 9755    });
 9756}
 9757
 9758#[gpui::test]
 9759async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9760    init_test(cx, |_| {});
 9761
 9762    let language = Arc::new(Language::new(
 9763        LanguageConfig::default(),
 9764        Some(tree_sitter_rust::LANGUAGE.into()),
 9765    ));
 9766
 9767    let text = r#"
 9768        use mod1::mod2::{mod3, mod4};
 9769
 9770        fn fn_1(param1: bool, param2: &str) {
 9771            let var1 = "hello world";
 9772        }
 9773    "#
 9774    .unindent();
 9775
 9776    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9777    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9778    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9779
 9780    editor
 9781        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9782        .await;
 9783
 9784    // Test 1: Cursor on a letter of a string word
 9785    editor.update_in(cx, |editor, window, cx| {
 9786        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9787            s.select_display_ranges([
 9788                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9789            ]);
 9790        });
 9791    });
 9792    editor.update_in(cx, |editor, window, cx| {
 9793        assert_text_with_selections(
 9794            editor,
 9795            indoc! {r#"
 9796                use mod1::mod2::{mod3, mod4};
 9797
 9798                fn fn_1(param1: bool, param2: &str) {
 9799                    let var1 = "hˇello world";
 9800                }
 9801            "#},
 9802            cx,
 9803        );
 9804        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9805        assert_text_with_selections(
 9806            editor,
 9807            indoc! {r#"
 9808                use mod1::mod2::{mod3, mod4};
 9809
 9810                fn fn_1(param1: bool, param2: &str) {
 9811                    let var1 = "«ˇhello» world";
 9812                }
 9813            "#},
 9814            cx,
 9815        );
 9816    });
 9817
 9818    // Test 2: Partial selection within a word
 9819    editor.update_in(cx, |editor, window, cx| {
 9820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9821            s.select_display_ranges([
 9822                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9823            ]);
 9824        });
 9825    });
 9826    editor.update_in(cx, |editor, window, cx| {
 9827        assert_text_with_selections(
 9828            editor,
 9829            indoc! {r#"
 9830                use mod1::mod2::{mod3, mod4};
 9831
 9832                fn fn_1(param1: bool, param2: &str) {
 9833                    let var1 = "h«elˇ»lo world";
 9834                }
 9835            "#},
 9836            cx,
 9837        );
 9838        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9839        assert_text_with_selections(
 9840            editor,
 9841            indoc! {r#"
 9842                use mod1::mod2::{mod3, mod4};
 9843
 9844                fn fn_1(param1: bool, param2: &str) {
 9845                    let var1 = "«ˇhello» world";
 9846                }
 9847            "#},
 9848            cx,
 9849        );
 9850    });
 9851
 9852    // Test 3: Complete word already selected
 9853    editor.update_in(cx, |editor, window, cx| {
 9854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9855            s.select_display_ranges([
 9856                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9857            ]);
 9858        });
 9859    });
 9860    editor.update_in(cx, |editor, window, cx| {
 9861        assert_text_with_selections(
 9862            editor,
 9863            indoc! {r#"
 9864                use mod1::mod2::{mod3, mod4};
 9865
 9866                fn fn_1(param1: bool, param2: &str) {
 9867                    let var1 = "«helloˇ» world";
 9868                }
 9869            "#},
 9870            cx,
 9871        );
 9872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9873        assert_text_with_selections(
 9874            editor,
 9875            indoc! {r#"
 9876                use mod1::mod2::{mod3, mod4};
 9877
 9878                fn fn_1(param1: bool, param2: &str) {
 9879                    let var1 = "«hello worldˇ»";
 9880                }
 9881            "#},
 9882            cx,
 9883        );
 9884    });
 9885
 9886    // Test 4: Selection spanning across words
 9887    editor.update_in(cx, |editor, window, cx| {
 9888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9889            s.select_display_ranges([
 9890                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9891            ]);
 9892        });
 9893    });
 9894    editor.update_in(cx, |editor, window, cx| {
 9895        assert_text_with_selections(
 9896            editor,
 9897            indoc! {r#"
 9898                use mod1::mod2::{mod3, mod4};
 9899
 9900                fn fn_1(param1: bool, param2: &str) {
 9901                    let var1 = "hel«lo woˇ»rld";
 9902                }
 9903            "#},
 9904            cx,
 9905        );
 9906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9907        assert_text_with_selections(
 9908            editor,
 9909            indoc! {r#"
 9910                use mod1::mod2::{mod3, mod4};
 9911
 9912                fn fn_1(param1: bool, param2: &str) {
 9913                    let var1 = "«ˇhello world»";
 9914                }
 9915            "#},
 9916            cx,
 9917        );
 9918    });
 9919
 9920    // Test 5: Expansion beyond string
 9921    editor.update_in(cx, |editor, window, cx| {
 9922        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9923        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9924        assert_text_with_selections(
 9925            editor,
 9926            indoc! {r#"
 9927                use mod1::mod2::{mod3, mod4};
 9928
 9929                fn fn_1(param1: bool, param2: &str) {
 9930                    «ˇlet var1 = "hello world";»
 9931                }
 9932            "#},
 9933            cx,
 9934        );
 9935    });
 9936}
 9937
 9938#[gpui::test]
 9939async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9940    init_test(cx, |_| {});
 9941
 9942    let mut cx = EditorTestContext::new(cx).await;
 9943
 9944    let language = Arc::new(Language::new(
 9945        LanguageConfig::default(),
 9946        Some(tree_sitter_rust::LANGUAGE.into()),
 9947    ));
 9948
 9949    cx.update_buffer(|buffer, cx| {
 9950        buffer.set_language(Some(language), cx);
 9951    });
 9952
 9953    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9954    cx.update_editor(|editor, window, cx| {
 9955        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9956    });
 9957
 9958    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9959
 9960    cx.set_state(indoc! { r#"fn a() {
 9961          // what
 9962          // a
 9963          // ˇlong
 9964          // method
 9965          // I
 9966          // sure
 9967          // hope
 9968          // it
 9969          // works
 9970    }"# });
 9971
 9972    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9973    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9974    cx.update(|_, cx| {
 9975        multi_buffer.update(cx, |multi_buffer, cx| {
 9976            multi_buffer.set_excerpts_for_path(
 9977                PathKey::for_buffer(&buffer, cx),
 9978                buffer,
 9979                [Point::new(1, 0)..Point::new(1, 0)],
 9980                3,
 9981                cx,
 9982            );
 9983        });
 9984    });
 9985
 9986    let editor2 = cx.new_window_entity(|window, cx| {
 9987        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9988    });
 9989
 9990    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9991    cx.update_editor(|editor, window, cx| {
 9992        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9993            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9994        })
 9995    });
 9996
 9997    cx.assert_editor_state(indoc! { "
 9998        fn a() {
 9999              // what
10000              // a
10001        ˇ      // long
10002              // method"});
10003
10004    cx.update_editor(|editor, window, cx| {
10005        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10006    });
10007
10008    // Although we could potentially make the action work when the syntax node
10009    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
10010    // did. Maybe we could also expand the excerpt to contain the range?
10011    cx.assert_editor_state(indoc! { "
10012        fn a() {
10013              // what
10014              // a
10015        ˇ      // long
10016              // method"});
10017}
10018
10019#[gpui::test]
10020async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10021    init_test(cx, |_| {});
10022
10023    let base_text = r#"
10024        impl A {
10025            // this is an uncommitted comment
10026
10027            fn b() {
10028                c();
10029            }
10030
10031            // this is another uncommitted comment
10032
10033            fn d() {
10034                // e
10035                // f
10036            }
10037        }
10038
10039        fn g() {
10040            // h
10041        }
10042    "#
10043    .unindent();
10044
10045    let text = r#"
10046        ˇimpl A {
10047
10048            fn b() {
10049                c();
10050            }
10051
10052            fn d() {
10053                // e
10054                // f
10055            }
10056        }
10057
10058        fn g() {
10059            // h
10060        }
10061    "#
10062    .unindent();
10063
10064    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10065    cx.set_state(&text);
10066    cx.set_head_text(&base_text);
10067    cx.update_editor(|editor, window, cx| {
10068        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10069    });
10070
10071    cx.assert_state_with_diff(
10072        "
10073        ˇimpl A {
10074      -     // this is an uncommitted comment
10075
10076            fn b() {
10077                c();
10078            }
10079
10080      -     // this is another uncommitted comment
10081      -
10082            fn d() {
10083                // e
10084                // f
10085            }
10086        }
10087
10088        fn g() {
10089            // h
10090        }
10091    "
10092        .unindent(),
10093    );
10094
10095    let expected_display_text = "
10096        impl A {
10097            // this is an uncommitted comment
10098
10099            fn b() {
1010010101            }
10102
10103            // this is another uncommitted comment
10104
10105            fn d() {
1010610107            }
10108        }
10109
10110        fn g() {
1011110112        }
10113        "
10114    .unindent();
10115
10116    cx.update_editor(|editor, window, cx| {
10117        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10118        assert_eq!(editor.display_text(cx), expected_display_text);
10119    });
10120}
10121
10122#[gpui::test]
10123async fn test_autoindent(cx: &mut TestAppContext) {
10124    init_test(cx, |_| {});
10125
10126    let language = Arc::new(
10127        Language::new(
10128            LanguageConfig {
10129                brackets: BracketPairConfig {
10130                    pairs: vec![
10131                        BracketPair {
10132                            start: "{".to_string(),
10133                            end: "}".to_string(),
10134                            close: false,
10135                            surround: false,
10136                            newline: true,
10137                        },
10138                        BracketPair {
10139                            start: "(".to_string(),
10140                            end: ")".to_string(),
10141                            close: false,
10142                            surround: false,
10143                            newline: true,
10144                        },
10145                    ],
10146                    ..Default::default()
10147                },
10148                ..Default::default()
10149            },
10150            Some(tree_sitter_rust::LANGUAGE.into()),
10151        )
10152        .with_indents_query(
10153            r#"
10154                (_ "(" ")" @end) @indent
10155                (_ "{" "}" @end) @indent
10156            "#,
10157        )
10158        .unwrap(),
10159    );
10160
10161    let text = "fn a() {}";
10162
10163    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10164    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10165    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10166    editor
10167        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10168        .await;
10169
10170    editor.update_in(cx, |editor, window, cx| {
10171        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10172            s.select_ranges([
10173                MultiBufferOffset(5)..MultiBufferOffset(5),
10174                MultiBufferOffset(8)..MultiBufferOffset(8),
10175                MultiBufferOffset(9)..MultiBufferOffset(9),
10176            ])
10177        });
10178        editor.newline(&Newline, window, cx);
10179        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10180        assert_eq!(
10181            editor.selections.ranges(&editor.display_snapshot(cx)),
10182            &[
10183                Point::new(1, 4)..Point::new(1, 4),
10184                Point::new(3, 4)..Point::new(3, 4),
10185                Point::new(5, 0)..Point::new(5, 0)
10186            ]
10187        );
10188    });
10189}
10190
10191#[gpui::test]
10192async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10193    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
10194
10195    let language = Arc::new(
10196        Language::new(
10197            LanguageConfig {
10198                brackets: BracketPairConfig {
10199                    pairs: vec![
10200                        BracketPair {
10201                            start: "{".to_string(),
10202                            end: "}".to_string(),
10203                            close: false,
10204                            surround: false,
10205                            newline: true,
10206                        },
10207                        BracketPair {
10208                            start: "(".to_string(),
10209                            end: ")".to_string(),
10210                            close: false,
10211                            surround: false,
10212                            newline: true,
10213                        },
10214                    ],
10215                    ..Default::default()
10216                },
10217                ..Default::default()
10218            },
10219            Some(tree_sitter_rust::LANGUAGE.into()),
10220        )
10221        .with_indents_query(
10222            r#"
10223                (_ "(" ")" @end) @indent
10224                (_ "{" "}" @end) @indent
10225            "#,
10226        )
10227        .unwrap(),
10228    );
10229
10230    let text = "fn a() {}";
10231
10232    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10233    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10234    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10235    editor
10236        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10237        .await;
10238
10239    editor.update_in(cx, |editor, window, cx| {
10240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10241            s.select_ranges([
10242                MultiBufferOffset(5)..MultiBufferOffset(5),
10243                MultiBufferOffset(8)..MultiBufferOffset(8),
10244                MultiBufferOffset(9)..MultiBufferOffset(9),
10245            ])
10246        });
10247        editor.newline(&Newline, window, cx);
10248        assert_eq!(
10249            editor.text(cx),
10250            indoc!(
10251                "
10252                fn a(
10253
10254                ) {
10255
10256                }
10257                "
10258            )
10259        );
10260        assert_eq!(
10261            editor.selections.ranges(&editor.display_snapshot(cx)),
10262            &[
10263                Point::new(1, 0)..Point::new(1, 0),
10264                Point::new(3, 0)..Point::new(3, 0),
10265                Point::new(5, 0)..Point::new(5, 0)
10266            ]
10267        );
10268    });
10269}
10270
10271#[gpui::test]
10272async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10273    init_test(cx, |settings| {
10274        settings.defaults.auto_indent = Some(true);
10275        settings.languages.0.insert(
10276            "python".into(),
10277            LanguageSettingsContent {
10278                auto_indent: Some(false),
10279                ..Default::default()
10280            },
10281        );
10282    });
10283
10284    let mut cx = EditorTestContext::new(cx).await;
10285
10286    let injected_language = Arc::new(
10287        Language::new(
10288            LanguageConfig {
10289                brackets: BracketPairConfig {
10290                    pairs: vec![
10291                        BracketPair {
10292                            start: "{".to_string(),
10293                            end: "}".to_string(),
10294                            close: false,
10295                            surround: false,
10296                            newline: true,
10297                        },
10298                        BracketPair {
10299                            start: "(".to_string(),
10300                            end: ")".to_string(),
10301                            close: true,
10302                            surround: false,
10303                            newline: true,
10304                        },
10305                    ],
10306                    ..Default::default()
10307                },
10308                name: "python".into(),
10309                ..Default::default()
10310            },
10311            Some(tree_sitter_python::LANGUAGE.into()),
10312        )
10313        .with_indents_query(
10314            r#"
10315                (_ "(" ")" @end) @indent
10316                (_ "{" "}" @end) @indent
10317            "#,
10318        )
10319        .unwrap(),
10320    );
10321
10322    let language = Arc::new(
10323        Language::new(
10324            LanguageConfig {
10325                brackets: BracketPairConfig {
10326                    pairs: vec![
10327                        BracketPair {
10328                            start: "{".to_string(),
10329                            end: "}".to_string(),
10330                            close: false,
10331                            surround: false,
10332                            newline: true,
10333                        },
10334                        BracketPair {
10335                            start: "(".to_string(),
10336                            end: ")".to_string(),
10337                            close: true,
10338                            surround: false,
10339                            newline: true,
10340                        },
10341                    ],
10342                    ..Default::default()
10343                },
10344                name: LanguageName::new_static("rust"),
10345                ..Default::default()
10346            },
10347            Some(tree_sitter_rust::LANGUAGE.into()),
10348        )
10349        .with_indents_query(
10350            r#"
10351                (_ "(" ")" @end) @indent
10352                (_ "{" "}" @end) @indent
10353            "#,
10354        )
10355        .unwrap()
10356        .with_injection_query(
10357            r#"
10358            (macro_invocation
10359                macro: (identifier) @_macro_name
10360                (token_tree) @injection.content
10361                (#set! injection.language "python"))
10362           "#,
10363        )
10364        .unwrap(),
10365    );
10366
10367    cx.language_registry().add(injected_language);
10368    cx.language_registry().add(language.clone());
10369
10370    cx.update_buffer(|buffer, cx| {
10371        buffer.set_language(Some(language), cx);
10372    });
10373
10374    cx.set_state(r#"struct A {ˇ}"#);
10375
10376    cx.update_editor(|editor, window, cx| {
10377        editor.newline(&Default::default(), window, cx);
10378    });
10379
10380    cx.assert_editor_state(indoc!(
10381        "struct A {
10382            ˇ
10383        }"
10384    ));
10385
10386    cx.set_state(r#"select_biased!(ˇ)"#);
10387
10388    cx.update_editor(|editor, window, cx| {
10389        editor.newline(&Default::default(), window, cx);
10390        editor.handle_input("def ", window, cx);
10391        editor.handle_input("(", window, cx);
10392        editor.newline(&Default::default(), window, cx);
10393        editor.handle_input("a", window, cx);
10394    });
10395
10396    cx.assert_editor_state(indoc!(
10397        "select_biased!(
10398        def (
1039910400        )
10401        )"
10402    ));
10403}
10404
10405#[gpui::test]
10406async fn test_autoindent_selections(cx: &mut TestAppContext) {
10407    init_test(cx, |_| {});
10408
10409    {
10410        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10411        cx.set_state(indoc! {"
10412            impl A {
10413
10414                fn b() {}
10415
10416            «fn c() {
10417
10418            }ˇ»
10419            }
10420        "});
10421
10422        cx.update_editor(|editor, window, cx| {
10423            editor.autoindent(&Default::default(), window, cx);
10424        });
10425        cx.wait_for_autoindent_applied().await;
10426
10427        cx.assert_editor_state(indoc! {"
10428            impl A {
10429
10430                fn b() {}
10431
10432                «fn c() {
10433
10434                }ˇ»
10435            }
10436        "});
10437    }
10438
10439    {
10440        let mut cx = EditorTestContext::new_multibuffer(
10441            cx,
10442            [indoc! { "
10443                impl A {
10444                «
10445                // a
10446                fn b(){}
10447                »
10448                «
10449                    }
10450                    fn c(){}
10451                »
10452            "}],
10453        );
10454
10455        let buffer = cx.update_editor(|editor, _, cx| {
10456            let buffer = editor.buffer().update(cx, |buffer, _| {
10457                buffer.all_buffers().iter().next().unwrap().clone()
10458            });
10459            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10460            buffer
10461        });
10462
10463        cx.run_until_parked();
10464        cx.update_editor(|editor, window, cx| {
10465            editor.select_all(&Default::default(), window, cx);
10466            editor.autoindent(&Default::default(), window, cx)
10467        });
10468        cx.run_until_parked();
10469
10470        cx.update(|_, cx| {
10471            assert_eq!(
10472                buffer.read(cx).text(),
10473                indoc! { "
10474                    impl A {
10475
10476                        // a
10477                        fn b(){}
10478
10479
10480                    }
10481                    fn c(){}
10482
10483                " }
10484            )
10485        });
10486    }
10487}
10488
10489#[gpui::test]
10490async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10491    init_test(cx, |_| {});
10492
10493    let mut cx = EditorTestContext::new(cx).await;
10494
10495    let language = Arc::new(Language::new(
10496        LanguageConfig {
10497            brackets: BracketPairConfig {
10498                pairs: vec![
10499                    BracketPair {
10500                        start: "{".to_string(),
10501                        end: "}".to_string(),
10502                        close: true,
10503                        surround: true,
10504                        newline: true,
10505                    },
10506                    BracketPair {
10507                        start: "(".to_string(),
10508                        end: ")".to_string(),
10509                        close: true,
10510                        surround: true,
10511                        newline: true,
10512                    },
10513                    BracketPair {
10514                        start: "/*".to_string(),
10515                        end: " */".to_string(),
10516                        close: true,
10517                        surround: true,
10518                        newline: true,
10519                    },
10520                    BracketPair {
10521                        start: "[".to_string(),
10522                        end: "]".to_string(),
10523                        close: false,
10524                        surround: false,
10525                        newline: true,
10526                    },
10527                    BracketPair {
10528                        start: "\"".to_string(),
10529                        end: "\"".to_string(),
10530                        close: true,
10531                        surround: true,
10532                        newline: false,
10533                    },
10534                    BracketPair {
10535                        start: "<".to_string(),
10536                        end: ">".to_string(),
10537                        close: false,
10538                        surround: true,
10539                        newline: true,
10540                    },
10541                ],
10542                ..Default::default()
10543            },
10544            autoclose_before: "})]".to_string(),
10545            ..Default::default()
10546        },
10547        Some(tree_sitter_rust::LANGUAGE.into()),
10548    ));
10549
10550    cx.language_registry().add(language.clone());
10551    cx.update_buffer(|buffer, cx| {
10552        buffer.set_language(Some(language), cx);
10553    });
10554
10555    cx.set_state(
10556        &r#"
10557            🏀ˇ
10558            εˇ
10559            ❤️ˇ
10560        "#
10561        .unindent(),
10562    );
10563
10564    // autoclose multiple nested brackets at multiple cursors
10565    cx.update_editor(|editor, window, cx| {
10566        editor.handle_input("{", window, cx);
10567        editor.handle_input("{", window, cx);
10568        editor.handle_input("{", window, cx);
10569    });
10570    cx.assert_editor_state(
10571        &"
10572            🏀{{{ˇ}}}
10573            ε{{{ˇ}}}
10574            ❤️{{{ˇ}}}
10575        "
10576        .unindent(),
10577    );
10578
10579    // insert a different closing bracket
10580    cx.update_editor(|editor, window, cx| {
10581        editor.handle_input(")", window, cx);
10582    });
10583    cx.assert_editor_state(
10584        &"
10585            🏀{{{)ˇ}}}
10586            ε{{{)ˇ}}}
10587            ❤️{{{)ˇ}}}
10588        "
10589        .unindent(),
10590    );
10591
10592    // skip over the auto-closed brackets when typing a closing bracket
10593    cx.update_editor(|editor, window, cx| {
10594        editor.move_right(&MoveRight, window, cx);
10595        editor.handle_input("}", window, cx);
10596        editor.handle_input("}", window, cx);
10597        editor.handle_input("}", window, cx);
10598    });
10599    cx.assert_editor_state(
10600        &"
10601            🏀{{{)}}}}ˇ
10602            ε{{{)}}}}ˇ
10603            ❤️{{{)}}}}ˇ
10604        "
10605        .unindent(),
10606    );
10607
10608    // autoclose multi-character pairs
10609    cx.set_state(
10610        &"
10611            ˇ
10612            ˇ
10613        "
10614        .unindent(),
10615    );
10616    cx.update_editor(|editor, window, cx| {
10617        editor.handle_input("/", window, cx);
10618        editor.handle_input("*", window, cx);
10619    });
10620    cx.assert_editor_state(
10621        &"
10622            /*ˇ */
10623            /*ˇ */
10624        "
10625        .unindent(),
10626    );
10627
10628    // one cursor autocloses a multi-character pair, one cursor
10629    // does not autoclose.
10630    cx.set_state(
10631        &"
1063210633            ˇ
10634        "
10635        .unindent(),
10636    );
10637    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10638    cx.assert_editor_state(
10639        &"
10640            /*ˇ */
1064110642        "
10643        .unindent(),
10644    );
10645
10646    // Don't autoclose if the next character isn't whitespace and isn't
10647    // listed in the language's "autoclose_before" section.
10648    cx.set_state("ˇa b");
10649    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10650    cx.assert_editor_state("{ˇa b");
10651
10652    // Don't autoclose if `close` is false for the bracket pair
10653    cx.set_state("ˇ");
10654    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10655    cx.assert_editor_state("");
10656
10657    // Surround with brackets if text is selected
10658    cx.set_state("«aˇ» b");
10659    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10660    cx.assert_editor_state("{«aˇ»} b");
10661
10662    // Autoclose when not immediately after a word character
10663    cx.set_state("a ˇ");
10664    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10665    cx.assert_editor_state("a \"ˇ\"");
10666
10667    // Autoclose pair where the start and end characters are the same
10668    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10669    cx.assert_editor_state("a \"\"ˇ");
10670
10671    // Don't autoclose when immediately after a word character
10672    cx.set_state("");
10673    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10674    cx.assert_editor_state("a\"ˇ");
10675
10676    // Do autoclose when after a non-word character
10677    cx.set_state("");
10678    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10679    cx.assert_editor_state("{\"ˇ\"");
10680
10681    // Non identical pairs autoclose regardless of preceding character
10682    cx.set_state("");
10683    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10684    cx.assert_editor_state("a{ˇ}");
10685
10686    // Don't autoclose pair if autoclose is disabled
10687    cx.set_state("ˇ");
10688    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10689    cx.assert_editor_state("");
10690
10691    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10692    cx.set_state("«aˇ» b");
10693    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10694    cx.assert_editor_state("<«aˇ»> b");
10695}
10696
10697#[gpui::test]
10698async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10699    init_test(cx, |settings| {
10700        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10701    });
10702
10703    let mut cx = EditorTestContext::new(cx).await;
10704
10705    let language = Arc::new(Language::new(
10706        LanguageConfig {
10707            brackets: BracketPairConfig {
10708                pairs: vec![
10709                    BracketPair {
10710                        start: "{".to_string(),
10711                        end: "}".to_string(),
10712                        close: true,
10713                        surround: true,
10714                        newline: true,
10715                    },
10716                    BracketPair {
10717                        start: "(".to_string(),
10718                        end: ")".to_string(),
10719                        close: true,
10720                        surround: true,
10721                        newline: true,
10722                    },
10723                    BracketPair {
10724                        start: "[".to_string(),
10725                        end: "]".to_string(),
10726                        close: false,
10727                        surround: false,
10728                        newline: true,
10729                    },
10730                ],
10731                ..Default::default()
10732            },
10733            autoclose_before: "})]".to_string(),
10734            ..Default::default()
10735        },
10736        Some(tree_sitter_rust::LANGUAGE.into()),
10737    ));
10738
10739    cx.language_registry().add(language.clone());
10740    cx.update_buffer(|buffer, cx| {
10741        buffer.set_language(Some(language), cx);
10742    });
10743
10744    cx.set_state(
10745        &"
10746            ˇ
10747            ˇ
10748            ˇ
10749        "
10750        .unindent(),
10751    );
10752
10753    // ensure only matching closing brackets are skipped over
10754    cx.update_editor(|editor, window, cx| {
10755        editor.handle_input("}", window, cx);
10756        editor.move_left(&MoveLeft, window, cx);
10757        editor.handle_input(")", window, cx);
10758        editor.move_left(&MoveLeft, window, cx);
10759    });
10760    cx.assert_editor_state(
10761        &"
10762            ˇ)}
10763            ˇ)}
10764            ˇ)}
10765        "
10766        .unindent(),
10767    );
10768
10769    // skip-over closing brackets at multiple cursors
10770    cx.update_editor(|editor, window, cx| {
10771        editor.handle_input(")", window, cx);
10772        editor.handle_input("}", window, cx);
10773    });
10774    cx.assert_editor_state(
10775        &"
10776            )}ˇ
10777            )}ˇ
10778            )}ˇ
10779        "
10780        .unindent(),
10781    );
10782
10783    // ignore non-close brackets
10784    cx.update_editor(|editor, window, cx| {
10785        editor.handle_input("]", window, cx);
10786        editor.move_left(&MoveLeft, window, cx);
10787        editor.handle_input("]", window, cx);
10788    });
10789    cx.assert_editor_state(
10790        &"
10791            )}]ˇ]
10792            )}]ˇ]
10793            )}]ˇ]
10794        "
10795        .unindent(),
10796    );
10797}
10798
10799#[gpui::test]
10800async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10801    init_test(cx, |_| {});
10802
10803    let mut cx = EditorTestContext::new(cx).await;
10804
10805    let html_language = Arc::new(
10806        Language::new(
10807            LanguageConfig {
10808                name: "HTML".into(),
10809                brackets: BracketPairConfig {
10810                    pairs: vec![
10811                        BracketPair {
10812                            start: "<".into(),
10813                            end: ">".into(),
10814                            close: true,
10815                            ..Default::default()
10816                        },
10817                        BracketPair {
10818                            start: "{".into(),
10819                            end: "}".into(),
10820                            close: true,
10821                            ..Default::default()
10822                        },
10823                        BracketPair {
10824                            start: "(".into(),
10825                            end: ")".into(),
10826                            close: true,
10827                            ..Default::default()
10828                        },
10829                    ],
10830                    ..Default::default()
10831                },
10832                autoclose_before: "})]>".into(),
10833                ..Default::default()
10834            },
10835            Some(tree_sitter_html::LANGUAGE.into()),
10836        )
10837        .with_injection_query(
10838            r#"
10839            (script_element
10840                (raw_text) @injection.content
10841                (#set! injection.language "javascript"))
10842            "#,
10843        )
10844        .unwrap(),
10845    );
10846
10847    let javascript_language = Arc::new(Language::new(
10848        LanguageConfig {
10849            name: "JavaScript".into(),
10850            brackets: BracketPairConfig {
10851                pairs: vec![
10852                    BracketPair {
10853                        start: "/*".into(),
10854                        end: " */".into(),
10855                        close: true,
10856                        ..Default::default()
10857                    },
10858                    BracketPair {
10859                        start: "{".into(),
10860                        end: "}".into(),
10861                        close: true,
10862                        ..Default::default()
10863                    },
10864                    BracketPair {
10865                        start: "(".into(),
10866                        end: ")".into(),
10867                        close: true,
10868                        ..Default::default()
10869                    },
10870                ],
10871                ..Default::default()
10872            },
10873            autoclose_before: "})]>".into(),
10874            ..Default::default()
10875        },
10876        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10877    ));
10878
10879    cx.language_registry().add(html_language.clone());
10880    cx.language_registry().add(javascript_language);
10881    cx.executor().run_until_parked();
10882
10883    cx.update_buffer(|buffer, cx| {
10884        buffer.set_language(Some(html_language), cx);
10885    });
10886
10887    cx.set_state(
10888        &r#"
10889            <body>ˇ
10890                <script>
10891                    var x = 1;ˇ
10892                </script>
10893            </body>ˇ
10894        "#
10895        .unindent(),
10896    );
10897
10898    // Precondition: different languages are active at different locations.
10899    cx.update_editor(|editor, window, cx| {
10900        let snapshot = editor.snapshot(window, cx);
10901        let cursors = editor
10902            .selections
10903            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10904        let languages = cursors
10905            .iter()
10906            .map(|c| snapshot.language_at(c.start).unwrap().name())
10907            .collect::<Vec<_>>();
10908        assert_eq!(
10909            languages,
10910            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10911        );
10912    });
10913
10914    // Angle brackets autoclose in HTML, but not JavaScript.
10915    cx.update_editor(|editor, window, cx| {
10916        editor.handle_input("<", window, cx);
10917        editor.handle_input("a", window, cx);
10918    });
10919    cx.assert_editor_state(
10920        &r#"
10921            <body><aˇ>
10922                <script>
10923                    var x = 1;<aˇ
10924                </script>
10925            </body><aˇ>
10926        "#
10927        .unindent(),
10928    );
10929
10930    // Curly braces and parens autoclose in both HTML and JavaScript.
10931    cx.update_editor(|editor, window, cx| {
10932        editor.handle_input(" b=", window, cx);
10933        editor.handle_input("{", window, cx);
10934        editor.handle_input("c", window, cx);
10935        editor.handle_input("(", window, cx);
10936    });
10937    cx.assert_editor_state(
10938        &r#"
10939            <body><a b={c(ˇ)}>
10940                <script>
10941                    var x = 1;<a b={c(ˇ)}
10942                </script>
10943            </body><a b={c(ˇ)}>
10944        "#
10945        .unindent(),
10946    );
10947
10948    // Brackets that were already autoclosed are skipped.
10949    cx.update_editor(|editor, window, cx| {
10950        editor.handle_input(")", window, cx);
10951        editor.handle_input("d", window, cx);
10952        editor.handle_input("}", window, cx);
10953    });
10954    cx.assert_editor_state(
10955        &r#"
10956            <body><a b={c()d}ˇ>
10957                <script>
10958                    var x = 1;<a b={c()d}ˇ
10959                </script>
10960            </body><a b={c()d}ˇ>
10961        "#
10962        .unindent(),
10963    );
10964    cx.update_editor(|editor, window, cx| {
10965        editor.handle_input(">", window, cx);
10966    });
10967    cx.assert_editor_state(
10968        &r#"
10969            <body><a b={c()d}>ˇ
10970                <script>
10971                    var x = 1;<a b={c()d}>ˇ
10972                </script>
10973            </body><a b={c()d}>ˇ
10974        "#
10975        .unindent(),
10976    );
10977
10978    // Reset
10979    cx.set_state(
10980        &r#"
10981            <body>ˇ
10982                <script>
10983                    var x = 1;ˇ
10984                </script>
10985            </body>ˇ
10986        "#
10987        .unindent(),
10988    );
10989
10990    cx.update_editor(|editor, window, cx| {
10991        editor.handle_input("<", window, cx);
10992    });
10993    cx.assert_editor_state(
10994        &r#"
10995            <body><ˇ>
10996                <script>
10997                    var x = 1;<ˇ
10998                </script>
10999            </body><ˇ>
11000        "#
11001        .unindent(),
11002    );
11003
11004    // When backspacing, the closing angle brackets are removed.
11005    cx.update_editor(|editor, window, cx| {
11006        editor.backspace(&Backspace, window, cx);
11007    });
11008    cx.assert_editor_state(
11009        &r#"
11010            <body>ˇ
11011                <script>
11012                    var x = 1;ˇ
11013                </script>
11014            </body>ˇ
11015        "#
11016        .unindent(),
11017    );
11018
11019    // Block comments autoclose in JavaScript, but not HTML.
11020    cx.update_editor(|editor, window, cx| {
11021        editor.handle_input("/", window, cx);
11022        editor.handle_input("*", window, cx);
11023    });
11024    cx.assert_editor_state(
11025        &r#"
11026            <body>/*ˇ
11027                <script>
11028                    var x = 1;/*ˇ */
11029                </script>
11030            </body>/*ˇ
11031        "#
11032        .unindent(),
11033    );
11034}
11035
11036#[gpui::test]
11037async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11038    init_test(cx, |_| {});
11039
11040    let mut cx = EditorTestContext::new(cx).await;
11041
11042    let rust_language = Arc::new(
11043        Language::new(
11044            LanguageConfig {
11045                name: "Rust".into(),
11046                brackets: serde_json::from_value(json!([
11047                    { "start": "{", "end": "}", "close": true, "newline": true },
11048                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11049                ]))
11050                .unwrap(),
11051                autoclose_before: "})]>".into(),
11052                ..Default::default()
11053            },
11054            Some(tree_sitter_rust::LANGUAGE.into()),
11055        )
11056        .with_override_query("(string_literal) @string")
11057        .unwrap(),
11058    );
11059
11060    cx.language_registry().add(rust_language.clone());
11061    cx.update_buffer(|buffer, cx| {
11062        buffer.set_language(Some(rust_language), cx);
11063    });
11064
11065    cx.set_state(
11066        &r#"
11067            let x = ˇ
11068        "#
11069        .unindent(),
11070    );
11071
11072    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11073    cx.update_editor(|editor, window, cx| {
11074        editor.handle_input("\"", window, cx);
11075    });
11076    cx.assert_editor_state(
11077        &r#"
11078            let x = "ˇ"
11079        "#
11080        .unindent(),
11081    );
11082
11083    // Inserting another quotation mark. The cursor moves across the existing
11084    // automatically-inserted quotation mark.
11085    cx.update_editor(|editor, window, cx| {
11086        editor.handle_input("\"", window, cx);
11087    });
11088    cx.assert_editor_state(
11089        &r#"
11090            let x = ""ˇ
11091        "#
11092        .unindent(),
11093    );
11094
11095    // Reset
11096    cx.set_state(
11097        &r#"
11098            let x = ˇ
11099        "#
11100        .unindent(),
11101    );
11102
11103    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11104    cx.update_editor(|editor, window, cx| {
11105        editor.handle_input("\"", window, cx);
11106        editor.handle_input(" ", window, cx);
11107        editor.move_left(&Default::default(), window, cx);
11108        editor.handle_input("\\", window, cx);
11109        editor.handle_input("\"", window, cx);
11110    });
11111    cx.assert_editor_state(
11112        &r#"
11113            let x = "\"ˇ "
11114        "#
11115        .unindent(),
11116    );
11117
11118    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11119    // mark. Nothing is inserted.
11120    cx.update_editor(|editor, window, cx| {
11121        editor.move_right(&Default::default(), window, cx);
11122        editor.handle_input("\"", window, cx);
11123    });
11124    cx.assert_editor_state(
11125        &r#"
11126            let x = "\" "ˇ
11127        "#
11128        .unindent(),
11129    );
11130}
11131
11132#[gpui::test]
11133async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11134    init_test(cx, |_| {});
11135
11136    let mut cx = EditorTestContext::new(cx).await;
11137    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11138
11139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11140
11141    // Double quote inside single-quoted string
11142    cx.set_state(indoc! {r#"
11143        def main():
11144            items = ['"', ˇ]
11145    "#});
11146    cx.update_editor(|editor, window, cx| {
11147        editor.handle_input("\"", window, cx);
11148    });
11149    cx.assert_editor_state(indoc! {r#"
11150        def main():
11151            items = ['"', "ˇ"]
11152    "#});
11153
11154    // Two double quotes inside single-quoted string
11155    cx.set_state(indoc! {r#"
11156        def main():
11157            items = ['""', ˇ]
11158    "#});
11159    cx.update_editor(|editor, window, cx| {
11160        editor.handle_input("\"", window, cx);
11161    });
11162    cx.assert_editor_state(indoc! {r#"
11163        def main():
11164            items = ['""', "ˇ"]
11165    "#});
11166
11167    // Single quote inside double-quoted string
11168    cx.set_state(indoc! {r#"
11169        def main():
11170            items = ["'", ˇ]
11171    "#});
11172    cx.update_editor(|editor, window, cx| {
11173        editor.handle_input("'", window, cx);
11174    });
11175    cx.assert_editor_state(indoc! {r#"
11176        def main():
11177            items = ["'", 'ˇ']
11178    "#});
11179
11180    // Two single quotes inside double-quoted string
11181    cx.set_state(indoc! {r#"
11182        def main():
11183            items = ["''", ˇ]
11184    "#});
11185    cx.update_editor(|editor, window, cx| {
11186        editor.handle_input("'", window, cx);
11187    });
11188    cx.assert_editor_state(indoc! {r#"
11189        def main():
11190            items = ["''", 'ˇ']
11191    "#});
11192
11193    // Mixed quotes on same line
11194    cx.set_state(indoc! {r#"
11195        def main():
11196            items = ['"""', "'''''", ˇ]
11197    "#});
11198    cx.update_editor(|editor, window, cx| {
11199        editor.handle_input("\"", window, cx);
11200    });
11201    cx.assert_editor_state(indoc! {r#"
11202        def main():
11203            items = ['"""', "'''''", "ˇ"]
11204    "#});
11205    cx.update_editor(|editor, window, cx| {
11206        editor.move_right(&MoveRight, window, cx);
11207    });
11208    cx.update_editor(|editor, window, cx| {
11209        editor.handle_input(", ", window, cx);
11210    });
11211    cx.update_editor(|editor, window, cx| {
11212        editor.handle_input("'", window, cx);
11213    });
11214    cx.assert_editor_state(indoc! {r#"
11215        def main():
11216            items = ['"""', "'''''", "", 'ˇ']
11217    "#});
11218}
11219
11220#[gpui::test]
11221async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
11222    init_test(cx, |_| {});
11223
11224    let mut cx = EditorTestContext::new(cx).await;
11225    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11226    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11227
11228    cx.set_state(indoc! {r#"
11229        def main():
11230            items = ["🎉", ˇ]
11231    "#});
11232    cx.update_editor(|editor, window, cx| {
11233        editor.handle_input("\"", window, cx);
11234    });
11235    cx.assert_editor_state(indoc! {r#"
11236        def main():
11237            items = ["🎉", "ˇ"]
11238    "#});
11239}
11240
11241#[gpui::test]
11242async fn test_surround_with_pair(cx: &mut TestAppContext) {
11243    init_test(cx, |_| {});
11244
11245    let language = Arc::new(Language::new(
11246        LanguageConfig {
11247            brackets: BracketPairConfig {
11248                pairs: vec![
11249                    BracketPair {
11250                        start: "{".to_string(),
11251                        end: "}".to_string(),
11252                        close: true,
11253                        surround: true,
11254                        newline: true,
11255                    },
11256                    BracketPair {
11257                        start: "/* ".to_string(),
11258                        end: "*/".to_string(),
11259                        close: true,
11260                        surround: true,
11261                        ..Default::default()
11262                    },
11263                ],
11264                ..Default::default()
11265            },
11266            ..Default::default()
11267        },
11268        Some(tree_sitter_rust::LANGUAGE.into()),
11269    ));
11270
11271    let text = r#"
11272        a
11273        b
11274        c
11275    "#
11276    .unindent();
11277
11278    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11279    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11280    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11281    editor
11282        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11283        .await;
11284
11285    editor.update_in(cx, |editor, window, cx| {
11286        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11287            s.select_display_ranges([
11288                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11289                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11290                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11291            ])
11292        });
11293
11294        editor.handle_input("{", window, cx);
11295        editor.handle_input("{", window, cx);
11296        editor.handle_input("{", window, cx);
11297        assert_eq!(
11298            editor.text(cx),
11299            "
11300                {{{a}}}
11301                {{{b}}}
11302                {{{c}}}
11303            "
11304            .unindent()
11305        );
11306        assert_eq!(
11307            display_ranges(editor, cx),
11308            [
11309                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11310                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11311                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11312            ]
11313        );
11314
11315        editor.undo(&Undo, window, cx);
11316        editor.undo(&Undo, window, cx);
11317        editor.undo(&Undo, window, cx);
11318        assert_eq!(
11319            editor.text(cx),
11320            "
11321                a
11322                b
11323                c
11324            "
11325            .unindent()
11326        );
11327        assert_eq!(
11328            display_ranges(editor, cx),
11329            [
11330                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11331                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11332                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11333            ]
11334        );
11335
11336        // Ensure inserting the first character of a multi-byte bracket pair
11337        // doesn't surround the selections with the bracket.
11338        editor.handle_input("/", window, cx);
11339        assert_eq!(
11340            editor.text(cx),
11341            "
11342                /
11343                /
11344                /
11345            "
11346            .unindent()
11347        );
11348        assert_eq!(
11349            display_ranges(editor, cx),
11350            [
11351                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11352                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11353                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11354            ]
11355        );
11356
11357        editor.undo(&Undo, window, cx);
11358        assert_eq!(
11359            editor.text(cx),
11360            "
11361                a
11362                b
11363                c
11364            "
11365            .unindent()
11366        );
11367        assert_eq!(
11368            display_ranges(editor, cx),
11369            [
11370                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11371                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11372                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11373            ]
11374        );
11375
11376        // Ensure inserting the last character of a multi-byte bracket pair
11377        // doesn't surround the selections with the bracket.
11378        editor.handle_input("*", window, cx);
11379        assert_eq!(
11380            editor.text(cx),
11381            "
11382                *
11383                *
11384                *
11385            "
11386            .unindent()
11387        );
11388        assert_eq!(
11389            display_ranges(editor, cx),
11390            [
11391                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11392                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11393                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11394            ]
11395        );
11396    });
11397}
11398
11399#[gpui::test]
11400async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11401    init_test(cx, |_| {});
11402
11403    let language = Arc::new(Language::new(
11404        LanguageConfig {
11405            brackets: BracketPairConfig {
11406                pairs: vec![BracketPair {
11407                    start: "{".to_string(),
11408                    end: "}".to_string(),
11409                    close: true,
11410                    surround: true,
11411                    newline: true,
11412                }],
11413                ..Default::default()
11414            },
11415            autoclose_before: "}".to_string(),
11416            ..Default::default()
11417        },
11418        Some(tree_sitter_rust::LANGUAGE.into()),
11419    ));
11420
11421    let text = r#"
11422        a
11423        b
11424        c
11425    "#
11426    .unindent();
11427
11428    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11429    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11430    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11431    editor
11432        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11433        .await;
11434
11435    editor.update_in(cx, |editor, window, cx| {
11436        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11437            s.select_ranges([
11438                Point::new(0, 1)..Point::new(0, 1),
11439                Point::new(1, 1)..Point::new(1, 1),
11440                Point::new(2, 1)..Point::new(2, 1),
11441            ])
11442        });
11443
11444        editor.handle_input("{", window, cx);
11445        editor.handle_input("{", window, cx);
11446        editor.handle_input("_", window, cx);
11447        assert_eq!(
11448            editor.text(cx),
11449            "
11450                a{{_}}
11451                b{{_}}
11452                c{{_}}
11453            "
11454            .unindent()
11455        );
11456        assert_eq!(
11457            editor
11458                .selections
11459                .ranges::<Point>(&editor.display_snapshot(cx)),
11460            [
11461                Point::new(0, 4)..Point::new(0, 4),
11462                Point::new(1, 4)..Point::new(1, 4),
11463                Point::new(2, 4)..Point::new(2, 4)
11464            ]
11465        );
11466
11467        editor.backspace(&Default::default(), window, cx);
11468        editor.backspace(&Default::default(), window, cx);
11469        assert_eq!(
11470            editor.text(cx),
11471            "
11472                a{}
11473                b{}
11474                c{}
11475            "
11476            .unindent()
11477        );
11478        assert_eq!(
11479            editor
11480                .selections
11481                .ranges::<Point>(&editor.display_snapshot(cx)),
11482            [
11483                Point::new(0, 2)..Point::new(0, 2),
11484                Point::new(1, 2)..Point::new(1, 2),
11485                Point::new(2, 2)..Point::new(2, 2)
11486            ]
11487        );
11488
11489        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11490        assert_eq!(
11491            editor.text(cx),
11492            "
11493                a
11494                b
11495                c
11496            "
11497            .unindent()
11498        );
11499        assert_eq!(
11500            editor
11501                .selections
11502                .ranges::<Point>(&editor.display_snapshot(cx)),
11503            [
11504                Point::new(0, 1)..Point::new(0, 1),
11505                Point::new(1, 1)..Point::new(1, 1),
11506                Point::new(2, 1)..Point::new(2, 1)
11507            ]
11508        );
11509    });
11510}
11511
11512#[gpui::test]
11513async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11514    init_test(cx, |settings| {
11515        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11516    });
11517
11518    let mut cx = EditorTestContext::new(cx).await;
11519
11520    let language = Arc::new(Language::new(
11521        LanguageConfig {
11522            brackets: BracketPairConfig {
11523                pairs: vec![
11524                    BracketPair {
11525                        start: "{".to_string(),
11526                        end: "}".to_string(),
11527                        close: true,
11528                        surround: true,
11529                        newline: true,
11530                    },
11531                    BracketPair {
11532                        start: "(".to_string(),
11533                        end: ")".to_string(),
11534                        close: true,
11535                        surround: true,
11536                        newline: true,
11537                    },
11538                    BracketPair {
11539                        start: "[".to_string(),
11540                        end: "]".to_string(),
11541                        close: false,
11542                        surround: true,
11543                        newline: true,
11544                    },
11545                ],
11546                ..Default::default()
11547            },
11548            autoclose_before: "})]".to_string(),
11549            ..Default::default()
11550        },
11551        Some(tree_sitter_rust::LANGUAGE.into()),
11552    ));
11553
11554    cx.language_registry().add(language.clone());
11555    cx.update_buffer(|buffer, cx| {
11556        buffer.set_language(Some(language), cx);
11557    });
11558
11559    cx.set_state(
11560        &"
11561            {(ˇ)}
11562            [[ˇ]]
11563            {(ˇ)}
11564        "
11565        .unindent(),
11566    );
11567
11568    cx.update_editor(|editor, window, cx| {
11569        editor.backspace(&Default::default(), window, cx);
11570        editor.backspace(&Default::default(), window, cx);
11571    });
11572
11573    cx.assert_editor_state(
11574        &"
11575            ˇ
11576            ˇ]]
11577            ˇ
11578        "
11579        .unindent(),
11580    );
11581
11582    cx.update_editor(|editor, window, cx| {
11583        editor.handle_input("{", window, cx);
11584        editor.handle_input("{", window, cx);
11585        editor.move_right(&MoveRight, window, cx);
11586        editor.move_right(&MoveRight, window, cx);
11587        editor.move_left(&MoveLeft, window, cx);
11588        editor.move_left(&MoveLeft, window, cx);
11589        editor.backspace(&Default::default(), window, cx);
11590    });
11591
11592    cx.assert_editor_state(
11593        &"
11594            {ˇ}
11595            {ˇ}]]
11596            {ˇ}
11597        "
11598        .unindent(),
11599    );
11600
11601    cx.update_editor(|editor, window, cx| {
11602        editor.backspace(&Default::default(), window, cx);
11603    });
11604
11605    cx.assert_editor_state(
11606        &"
11607            ˇ
11608            ˇ]]
11609            ˇ
11610        "
11611        .unindent(),
11612    );
11613}
11614
11615#[gpui::test]
11616async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11617    init_test(cx, |_| {});
11618
11619    let language = Arc::new(Language::new(
11620        LanguageConfig::default(),
11621        Some(tree_sitter_rust::LANGUAGE.into()),
11622    ));
11623
11624    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11625    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11626    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11627    editor
11628        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11629        .await;
11630
11631    editor.update_in(cx, |editor, window, cx| {
11632        editor.set_auto_replace_emoji_shortcode(true);
11633
11634        editor.handle_input("Hello ", window, cx);
11635        editor.handle_input(":wave", window, cx);
11636        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11637
11638        editor.handle_input(":", window, cx);
11639        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11640
11641        editor.handle_input(" :smile", window, cx);
11642        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11643
11644        editor.handle_input(":", window, cx);
11645        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11646
11647        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11648        editor.handle_input(":wave", window, cx);
11649        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11650
11651        editor.handle_input(":", window, cx);
11652        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11653
11654        editor.handle_input(":1", window, cx);
11655        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11656
11657        editor.handle_input(":", window, cx);
11658        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11659
11660        // Ensure shortcode does not get replaced when it is part of a word
11661        editor.handle_input(" Test:wave", window, cx);
11662        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11663
11664        editor.handle_input(":", window, cx);
11665        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11666
11667        editor.set_auto_replace_emoji_shortcode(false);
11668
11669        // Ensure shortcode does not get replaced when auto replace is off
11670        editor.handle_input(" :wave", window, cx);
11671        assert_eq!(
11672            editor.text(cx),
11673            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11674        );
11675
11676        editor.handle_input(":", window, cx);
11677        assert_eq!(
11678            editor.text(cx),
11679            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11680        );
11681    });
11682}
11683
11684#[gpui::test]
11685async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11686    init_test(cx, |_| {});
11687
11688    let (text, insertion_ranges) = marked_text_ranges(
11689        indoc! {"
11690            ˇ
11691        "},
11692        false,
11693    );
11694
11695    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11696    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11697
11698    _ = editor.update_in(cx, |editor, window, cx| {
11699        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11700
11701        editor
11702            .insert_snippet(
11703                &insertion_ranges
11704                    .iter()
11705                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11706                    .collect::<Vec<_>>(),
11707                snippet,
11708                window,
11709                cx,
11710            )
11711            .unwrap();
11712
11713        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11714            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11715            assert_eq!(editor.text(cx), expected_text);
11716            assert_eq!(
11717                editor.selections.ranges(&editor.display_snapshot(cx)),
11718                selection_ranges
11719                    .iter()
11720                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11721                    .collect::<Vec<_>>()
11722            );
11723        }
11724
11725        assert(
11726            editor,
11727            cx,
11728            indoc! {"
11729            type «» =•
11730            "},
11731        );
11732
11733        assert!(editor.context_menu_visible(), "There should be a matches");
11734    });
11735}
11736
11737#[gpui::test]
11738async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11739    init_test(cx, |_| {});
11740
11741    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11742        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11743        assert_eq!(editor.text(cx), expected_text);
11744        assert_eq!(
11745            editor.selections.ranges(&editor.display_snapshot(cx)),
11746            selection_ranges
11747                .iter()
11748                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11749                .collect::<Vec<_>>()
11750        );
11751    }
11752
11753    let (text, insertion_ranges) = marked_text_ranges(
11754        indoc! {"
11755            ˇ
11756        "},
11757        false,
11758    );
11759
11760    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11761    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11762
11763    _ = editor.update_in(cx, |editor, window, cx| {
11764        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11765
11766        editor
11767            .insert_snippet(
11768                &insertion_ranges
11769                    .iter()
11770                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11771                    .collect::<Vec<_>>(),
11772                snippet,
11773                window,
11774                cx,
11775            )
11776            .unwrap();
11777
11778        assert_state(
11779            editor,
11780            cx,
11781            indoc! {"
11782            type «» = ;•
11783            "},
11784        );
11785
11786        assert!(
11787            editor.context_menu_visible(),
11788            "Context menu should be visible for placeholder choices"
11789        );
11790
11791        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11792
11793        assert_state(
11794            editor,
11795            cx,
11796            indoc! {"
11797            type  = «»;•
11798            "},
11799        );
11800
11801        assert!(
11802            !editor.context_menu_visible(),
11803            "Context menu should be hidden after moving to next tabstop"
11804        );
11805
11806        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11807
11808        assert_state(
11809            editor,
11810            cx,
11811            indoc! {"
11812            type  = ; ˇ
11813            "},
11814        );
11815
11816        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11817
11818        assert_state(
11819            editor,
11820            cx,
11821            indoc! {"
11822            type  = ; ˇ
11823            "},
11824        );
11825    });
11826
11827    _ = editor.update_in(cx, |editor, window, cx| {
11828        editor.select_all(&SelectAll, window, cx);
11829        editor.backspace(&Backspace, window, cx);
11830
11831        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11832        let insertion_ranges = editor
11833            .selections
11834            .all(&editor.display_snapshot(cx))
11835            .iter()
11836            .map(|s| s.range())
11837            .collect::<Vec<_>>();
11838
11839        editor
11840            .insert_snippet(&insertion_ranges, snippet, window, cx)
11841            .unwrap();
11842
11843        assert_state(editor, cx, "fn «» = value;•");
11844
11845        assert!(
11846            editor.context_menu_visible(),
11847            "Context menu should be visible for placeholder choices"
11848        );
11849
11850        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11851
11852        assert_state(editor, cx, "fn  = «valueˇ»;•");
11853
11854        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11855
11856        assert_state(editor, cx, "fn «» = value;•");
11857
11858        assert!(
11859            editor.context_menu_visible(),
11860            "Context menu should be visible again after returning to first tabstop"
11861        );
11862
11863        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11864
11865        assert_state(editor, cx, "fn «» = value;•");
11866    });
11867}
11868
11869#[gpui::test]
11870async fn test_snippets(cx: &mut TestAppContext) {
11871    init_test(cx, |_| {});
11872
11873    let mut cx = EditorTestContext::new(cx).await;
11874
11875    cx.set_state(indoc! {"
11876        a.ˇ b
11877        a.ˇ b
11878        a.ˇ b
11879    "});
11880
11881    cx.update_editor(|editor, window, cx| {
11882        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11883        let insertion_ranges = editor
11884            .selections
11885            .all(&editor.display_snapshot(cx))
11886            .iter()
11887            .map(|s| s.range())
11888            .collect::<Vec<_>>();
11889        editor
11890            .insert_snippet(&insertion_ranges, snippet, window, cx)
11891            .unwrap();
11892    });
11893
11894    cx.assert_editor_state(indoc! {"
11895        a.f(«oneˇ», two, «threeˇ») b
11896        a.f(«oneˇ», two, «threeˇ») b
11897        a.f(«oneˇ», two, «threeˇ») b
11898    "});
11899
11900    // Can't move earlier than the first tab stop
11901    cx.update_editor(|editor, window, cx| {
11902        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11903    });
11904    cx.assert_editor_state(indoc! {"
11905        a.f(«oneˇ», two, «threeˇ») b
11906        a.f(«oneˇ», two, «threeˇ») b
11907        a.f(«oneˇ», two, «threeˇ») b
11908    "});
11909
11910    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11911    cx.assert_editor_state(indoc! {"
11912        a.f(one, «twoˇ», three) b
11913        a.f(one, «twoˇ», three) b
11914        a.f(one, «twoˇ», three) b
11915    "});
11916
11917    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11918    cx.assert_editor_state(indoc! {"
11919        a.f(«oneˇ», two, «threeˇ») b
11920        a.f(«oneˇ», two, «threeˇ») b
11921        a.f(«oneˇ», two, «threeˇ») b
11922    "});
11923
11924    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11925    cx.assert_editor_state(indoc! {"
11926        a.f(one, «twoˇ», three) b
11927        a.f(one, «twoˇ», three) b
11928        a.f(one, «twoˇ», three) b
11929    "});
11930    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11931    cx.assert_editor_state(indoc! {"
11932        a.f(one, two, three)ˇ b
11933        a.f(one, two, three)ˇ b
11934        a.f(one, two, three)ˇ b
11935    "});
11936
11937    // As soon as the last tab stop is reached, snippet state is gone
11938    cx.update_editor(|editor, window, cx| {
11939        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11940    });
11941    cx.assert_editor_state(indoc! {"
11942        a.f(one, two, three)ˇ b
11943        a.f(one, two, three)ˇ b
11944        a.f(one, two, three)ˇ b
11945    "});
11946}
11947
11948#[gpui::test]
11949async fn test_snippet_indentation(cx: &mut TestAppContext) {
11950    init_test(cx, |_| {});
11951
11952    let mut cx = EditorTestContext::new(cx).await;
11953
11954    cx.update_editor(|editor, window, cx| {
11955        let snippet = Snippet::parse(indoc! {"
11956            /*
11957             * Multiline comment with leading indentation
11958             *
11959             * $1
11960             */
11961            $0"})
11962        .unwrap();
11963        let insertion_ranges = editor
11964            .selections
11965            .all(&editor.display_snapshot(cx))
11966            .iter()
11967            .map(|s| s.range())
11968            .collect::<Vec<_>>();
11969        editor
11970            .insert_snippet(&insertion_ranges, snippet, window, cx)
11971            .unwrap();
11972    });
11973
11974    cx.assert_editor_state(indoc! {"
11975        /*
11976         * Multiline comment with leading indentation
11977         *
11978         * ˇ
11979         */
11980    "});
11981
11982    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11983    cx.assert_editor_state(indoc! {"
11984        /*
11985         * Multiline comment with leading indentation
11986         *
11987         *•
11988         */
11989        ˇ"});
11990}
11991
11992#[gpui::test]
11993async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11994    init_test(cx, |_| {});
11995
11996    let mut cx = EditorTestContext::new(cx).await;
11997    cx.update_editor(|editor, _, cx| {
11998        editor.project().unwrap().update(cx, |project, cx| {
11999            project.snippets().update(cx, |snippets, _cx| {
12000                let snippet = project::snippet_provider::Snippet {
12001                    prefix: vec!["multi word".to_string()],
12002                    body: "this is many words".to_string(),
12003                    description: Some("description".to_string()),
12004                    name: "multi-word snippet test".to_string(),
12005                };
12006                snippets.add_snippet_for_test(
12007                    None,
12008                    PathBuf::from("test_snippets.json"),
12009                    vec![Arc::new(snippet)],
12010                );
12011            });
12012        })
12013    });
12014
12015    for (input_to_simulate, should_match_snippet) in [
12016        ("m", true),
12017        ("m ", true),
12018        ("m w", true),
12019        ("aa m w", true),
12020        ("aa m g", false),
12021    ] {
12022        cx.set_state("ˇ");
12023        cx.simulate_input(input_to_simulate); // fails correctly
12024
12025        cx.update_editor(|editor, _, _| {
12026            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12027            else {
12028                assert!(!should_match_snippet); // no completions! don't even show the menu
12029                return;
12030            };
12031            assert!(context_menu.visible());
12032            let completions = context_menu.completions.borrow();
12033
12034            assert_eq!(!completions.is_empty(), should_match_snippet);
12035        });
12036    }
12037}
12038
12039#[gpui::test]
12040async fn test_document_format_during_save(cx: &mut TestAppContext) {
12041    init_test(cx, |_| {});
12042
12043    let fs = FakeFs::new(cx.executor());
12044    fs.insert_file(path!("/file.rs"), Default::default()).await;
12045
12046    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12047
12048    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12049    language_registry.add(rust_lang());
12050    let mut fake_servers = language_registry.register_fake_lsp(
12051        "Rust",
12052        FakeLspAdapter {
12053            capabilities: lsp::ServerCapabilities {
12054                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12055                ..Default::default()
12056            },
12057            ..Default::default()
12058        },
12059    );
12060
12061    let buffer = project
12062        .update(cx, |project, cx| {
12063            project.open_local_buffer(path!("/file.rs"), cx)
12064        })
12065        .await
12066        .unwrap();
12067
12068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12069    let (editor, cx) = cx.add_window_view(|window, cx| {
12070        build_editor_with_project(project.clone(), buffer, window, cx)
12071    });
12072    editor.update_in(cx, |editor, window, cx| {
12073        editor.set_text("one\ntwo\nthree\n", window, cx)
12074    });
12075    assert!(cx.read(|cx| editor.is_dirty(cx)));
12076
12077    let fake_server = fake_servers.next().await.unwrap();
12078
12079    {
12080        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12081            move |params, _| async move {
12082                assert_eq!(
12083                    params.text_document.uri,
12084                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12085                );
12086                assert_eq!(params.options.tab_size, 4);
12087                Ok(Some(vec![lsp::TextEdit::new(
12088                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12089                    ", ".to_string(),
12090                )]))
12091            },
12092        );
12093        let save = editor
12094            .update_in(cx, |editor, window, cx| {
12095                editor.save(
12096                    SaveOptions {
12097                        format: true,
12098                        autosave: false,
12099                    },
12100                    project.clone(),
12101                    window,
12102                    cx,
12103                )
12104            })
12105            .unwrap();
12106        save.await;
12107
12108        assert_eq!(
12109            editor.update(cx, |editor, cx| editor.text(cx)),
12110            "one, two\nthree\n"
12111        );
12112        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12113    }
12114
12115    {
12116        editor.update_in(cx, |editor, window, cx| {
12117            editor.set_text("one\ntwo\nthree\n", window, cx)
12118        });
12119        assert!(cx.read(|cx| editor.is_dirty(cx)));
12120
12121        // Ensure we can still save even if formatting hangs.
12122        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12123            move |params, _| async move {
12124                assert_eq!(
12125                    params.text_document.uri,
12126                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12127                );
12128                futures::future::pending::<()>().await;
12129                unreachable!()
12130            },
12131        );
12132        let save = editor
12133            .update_in(cx, |editor, window, cx| {
12134                editor.save(
12135                    SaveOptions {
12136                        format: true,
12137                        autosave: false,
12138                    },
12139                    project.clone(),
12140                    window,
12141                    cx,
12142                )
12143            })
12144            .unwrap();
12145        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12146        save.await;
12147        assert_eq!(
12148            editor.update(cx, |editor, cx| editor.text(cx)),
12149            "one\ntwo\nthree\n"
12150        );
12151    }
12152
12153    // Set rust language override and assert overridden tabsize is sent to language server
12154    update_test_language_settings(cx, |settings| {
12155        settings.languages.0.insert(
12156            "Rust".into(),
12157            LanguageSettingsContent {
12158                tab_size: NonZeroU32::new(8),
12159                ..Default::default()
12160            },
12161        );
12162    });
12163
12164    {
12165        editor.update_in(cx, |editor, window, cx| {
12166            editor.set_text("somehting_new\n", window, cx)
12167        });
12168        assert!(cx.read(|cx| editor.is_dirty(cx)));
12169        let _formatting_request_signal = fake_server
12170            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12171                assert_eq!(
12172                    params.text_document.uri,
12173                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12174                );
12175                assert_eq!(params.options.tab_size, 8);
12176                Ok(Some(vec![]))
12177            });
12178        let save = editor
12179            .update_in(cx, |editor, window, cx| {
12180                editor.save(
12181                    SaveOptions {
12182                        format: true,
12183                        autosave: false,
12184                    },
12185                    project.clone(),
12186                    window,
12187                    cx,
12188                )
12189            })
12190            .unwrap();
12191        save.await;
12192    }
12193}
12194
12195#[gpui::test]
12196async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
12197    init_test(cx, |settings| {
12198        settings.defaults.ensure_final_newline_on_save = Some(false);
12199    });
12200
12201    let fs = FakeFs::new(cx.executor());
12202    fs.insert_file(path!("/file.txt"), "foo".into()).await;
12203
12204    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
12205
12206    let buffer = project
12207        .update(cx, |project, cx| {
12208            project.open_local_buffer(path!("/file.txt"), cx)
12209        })
12210        .await
12211        .unwrap();
12212
12213    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12214    let (editor, cx) = cx.add_window_view(|window, cx| {
12215        build_editor_with_project(project.clone(), buffer, window, cx)
12216    });
12217    editor.update_in(cx, |editor, window, cx| {
12218        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
12219            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
12220        });
12221    });
12222    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12223
12224    editor.update_in(cx, |editor, window, cx| {
12225        editor.handle_input("\n", window, cx)
12226    });
12227    cx.run_until_parked();
12228    save(&editor, &project, cx).await;
12229    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12230
12231    editor.update_in(cx, |editor, window, cx| {
12232        editor.undo(&Default::default(), window, cx);
12233    });
12234    save(&editor, &project, cx).await;
12235    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12236
12237    editor.update_in(cx, |editor, window, cx| {
12238        editor.redo(&Default::default(), window, cx);
12239    });
12240    cx.run_until_parked();
12241    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12242
12243    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
12244        let save = editor
12245            .update_in(cx, |editor, window, cx| {
12246                editor.save(
12247                    SaveOptions {
12248                        format: true,
12249                        autosave: false,
12250                    },
12251                    project.clone(),
12252                    window,
12253                    cx,
12254                )
12255            })
12256            .unwrap();
12257        save.await;
12258        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12259    }
12260}
12261
12262#[gpui::test]
12263async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12264    init_test(cx, |_| {});
12265
12266    let cols = 4;
12267    let rows = 10;
12268    let sample_text_1 = sample_text(rows, cols, 'a');
12269    assert_eq!(
12270        sample_text_1,
12271        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12272    );
12273    let sample_text_2 = sample_text(rows, cols, 'l');
12274    assert_eq!(
12275        sample_text_2,
12276        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12277    );
12278    let sample_text_3 = sample_text(rows, cols, 'v');
12279    assert_eq!(
12280        sample_text_3,
12281        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12282    );
12283
12284    let fs = FakeFs::new(cx.executor());
12285    fs.insert_tree(
12286        path!("/a"),
12287        json!({
12288            "main.rs": sample_text_1,
12289            "other.rs": sample_text_2,
12290            "lib.rs": sample_text_3,
12291        }),
12292    )
12293    .await;
12294
12295    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12296    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12297    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12298
12299    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12300    language_registry.add(rust_lang());
12301    let mut fake_servers = language_registry.register_fake_lsp(
12302        "Rust",
12303        FakeLspAdapter {
12304            capabilities: lsp::ServerCapabilities {
12305                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12306                ..Default::default()
12307            },
12308            ..Default::default()
12309        },
12310    );
12311
12312    let worktree = project.update(cx, |project, cx| {
12313        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12314        assert_eq!(worktrees.len(), 1);
12315        worktrees.pop().unwrap()
12316    });
12317    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12318
12319    let buffer_1 = project
12320        .update(cx, |project, cx| {
12321            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12322        })
12323        .await
12324        .unwrap();
12325    let buffer_2 = project
12326        .update(cx, |project, cx| {
12327            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12328        })
12329        .await
12330        .unwrap();
12331    let buffer_3 = project
12332        .update(cx, |project, cx| {
12333            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12334        })
12335        .await
12336        .unwrap();
12337
12338    let multi_buffer = cx.new(|cx| {
12339        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12340        multi_buffer.push_excerpts(
12341            buffer_1.clone(),
12342            [
12343                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12344                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12345                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12346            ],
12347            cx,
12348        );
12349        multi_buffer.push_excerpts(
12350            buffer_2.clone(),
12351            [
12352                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12353                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12354                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12355            ],
12356            cx,
12357        );
12358        multi_buffer.push_excerpts(
12359            buffer_3.clone(),
12360            [
12361                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12362                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12363                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12364            ],
12365            cx,
12366        );
12367        multi_buffer
12368    });
12369    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12370        Editor::new(
12371            EditorMode::full(),
12372            multi_buffer,
12373            Some(project.clone()),
12374            window,
12375            cx,
12376        )
12377    });
12378
12379    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12380        editor.change_selections(
12381            SelectionEffects::scroll(Autoscroll::Next),
12382            window,
12383            cx,
12384            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12385        );
12386        editor.insert("|one|two|three|", window, cx);
12387    });
12388    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12389    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12390        editor.change_selections(
12391            SelectionEffects::scroll(Autoscroll::Next),
12392            window,
12393            cx,
12394            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12395        );
12396        editor.insert("|four|five|six|", window, cx);
12397    });
12398    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12399
12400    // First two buffers should be edited, but not the third one.
12401    assert_eq!(
12402        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12403        "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}",
12404    );
12405    buffer_1.update(cx, |buffer, _| {
12406        assert!(buffer.is_dirty());
12407        assert_eq!(
12408            buffer.text(),
12409            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12410        )
12411    });
12412    buffer_2.update(cx, |buffer, _| {
12413        assert!(buffer.is_dirty());
12414        assert_eq!(
12415            buffer.text(),
12416            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12417        )
12418    });
12419    buffer_3.update(cx, |buffer, _| {
12420        assert!(!buffer.is_dirty());
12421        assert_eq!(buffer.text(), sample_text_3,)
12422    });
12423    cx.executor().run_until_parked();
12424
12425    let save = multi_buffer_editor
12426        .update_in(cx, |editor, window, cx| {
12427            editor.save(
12428                SaveOptions {
12429                    format: true,
12430                    autosave: false,
12431                },
12432                project.clone(),
12433                window,
12434                cx,
12435            )
12436        })
12437        .unwrap();
12438
12439    let fake_server = fake_servers.next().await.unwrap();
12440    fake_server
12441        .server
12442        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12443            Ok(Some(vec![lsp::TextEdit::new(
12444                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12445                format!("[{} formatted]", params.text_document.uri),
12446            )]))
12447        })
12448        .detach();
12449    save.await;
12450
12451    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12452    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12453    assert_eq!(
12454        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12455        uri!(
12456            "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}"
12457        ),
12458    );
12459    buffer_1.update(cx, |buffer, _| {
12460        assert!(!buffer.is_dirty());
12461        assert_eq!(
12462            buffer.text(),
12463            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12464        )
12465    });
12466    buffer_2.update(cx, |buffer, _| {
12467        assert!(!buffer.is_dirty());
12468        assert_eq!(
12469            buffer.text(),
12470            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12471        )
12472    });
12473    buffer_3.update(cx, |buffer, _| {
12474        assert!(!buffer.is_dirty());
12475        assert_eq!(buffer.text(), sample_text_3,)
12476    });
12477}
12478
12479#[gpui::test]
12480async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12481    init_test(cx, |_| {});
12482
12483    let fs = FakeFs::new(cx.executor());
12484    fs.insert_tree(
12485        path!("/dir"),
12486        json!({
12487            "file1.rs": "fn main() { println!(\"hello\"); }",
12488            "file2.rs": "fn test() { println!(\"test\"); }",
12489            "file3.rs": "fn other() { println!(\"other\"); }\n",
12490        }),
12491    )
12492    .await;
12493
12494    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12495    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12496    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12497
12498    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12499    language_registry.add(rust_lang());
12500
12501    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12502    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12503
12504    // Open three buffers
12505    let buffer_1 = project
12506        .update(cx, |project, cx| {
12507            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12508        })
12509        .await
12510        .unwrap();
12511    let buffer_2 = project
12512        .update(cx, |project, cx| {
12513            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12514        })
12515        .await
12516        .unwrap();
12517    let buffer_3 = project
12518        .update(cx, |project, cx| {
12519            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12520        })
12521        .await
12522        .unwrap();
12523
12524    // Create a multi-buffer with all three buffers
12525    let multi_buffer = cx.new(|cx| {
12526        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12527        multi_buffer.push_excerpts(
12528            buffer_1.clone(),
12529            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12530            cx,
12531        );
12532        multi_buffer.push_excerpts(
12533            buffer_2.clone(),
12534            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12535            cx,
12536        );
12537        multi_buffer.push_excerpts(
12538            buffer_3.clone(),
12539            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12540            cx,
12541        );
12542        multi_buffer
12543    });
12544
12545    let editor = cx.new_window_entity(|window, cx| {
12546        Editor::new(
12547            EditorMode::full(),
12548            multi_buffer,
12549            Some(project.clone()),
12550            window,
12551            cx,
12552        )
12553    });
12554
12555    // Edit only the first buffer
12556    editor.update_in(cx, |editor, window, cx| {
12557        editor.change_selections(
12558            SelectionEffects::scroll(Autoscroll::Next),
12559            window,
12560            cx,
12561            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12562        );
12563        editor.insert("// edited", window, cx);
12564    });
12565
12566    // Verify that only buffer 1 is dirty
12567    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12568    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12569    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12570
12571    // Get write counts after file creation (files were created with initial content)
12572    // We expect each file to have been written once during creation
12573    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12574    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12575    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12576
12577    // Perform autosave
12578    let save_task = editor.update_in(cx, |editor, window, cx| {
12579        editor.save(
12580            SaveOptions {
12581                format: true,
12582                autosave: true,
12583            },
12584            project.clone(),
12585            window,
12586            cx,
12587        )
12588    });
12589    save_task.await.unwrap();
12590
12591    // Only the dirty buffer should have been saved
12592    assert_eq!(
12593        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12594        1,
12595        "Buffer 1 was dirty, so it should have been written once during autosave"
12596    );
12597    assert_eq!(
12598        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12599        0,
12600        "Buffer 2 was clean, so it should not have been written during autosave"
12601    );
12602    assert_eq!(
12603        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12604        0,
12605        "Buffer 3 was clean, so it should not have been written during autosave"
12606    );
12607
12608    // Verify buffer states after autosave
12609    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12610    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12611    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12612
12613    // Now perform a manual save (format = true)
12614    let save_task = editor.update_in(cx, |editor, window, cx| {
12615        editor.save(
12616            SaveOptions {
12617                format: true,
12618                autosave: false,
12619            },
12620            project.clone(),
12621            window,
12622            cx,
12623        )
12624    });
12625    save_task.await.unwrap();
12626
12627    // During manual save, clean buffers don't get written to disk
12628    // They just get did_save called for language server notifications
12629    assert_eq!(
12630        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12631        1,
12632        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12633    );
12634    assert_eq!(
12635        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12636        0,
12637        "Buffer 2 should not have been written at all"
12638    );
12639    assert_eq!(
12640        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12641        0,
12642        "Buffer 3 should not have been written at all"
12643    );
12644}
12645
12646async fn setup_range_format_test(
12647    cx: &mut TestAppContext,
12648) -> (
12649    Entity<Project>,
12650    Entity<Editor>,
12651    &mut gpui::VisualTestContext,
12652    lsp::FakeLanguageServer,
12653) {
12654    init_test(cx, |_| {});
12655
12656    let fs = FakeFs::new(cx.executor());
12657    fs.insert_file(path!("/file.rs"), Default::default()).await;
12658
12659    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12660
12661    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12662    language_registry.add(rust_lang());
12663    let mut fake_servers = language_registry.register_fake_lsp(
12664        "Rust",
12665        FakeLspAdapter {
12666            capabilities: lsp::ServerCapabilities {
12667                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12668                ..lsp::ServerCapabilities::default()
12669            },
12670            ..FakeLspAdapter::default()
12671        },
12672    );
12673
12674    let buffer = project
12675        .update(cx, |project, cx| {
12676            project.open_local_buffer(path!("/file.rs"), cx)
12677        })
12678        .await
12679        .unwrap();
12680
12681    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12682    let (editor, cx) = cx.add_window_view(|window, cx| {
12683        build_editor_with_project(project.clone(), buffer, window, cx)
12684    });
12685
12686    let fake_server = fake_servers.next().await.unwrap();
12687
12688    (project, editor, cx, fake_server)
12689}
12690
12691#[gpui::test]
12692async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12693    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12694
12695    editor.update_in(cx, |editor, window, cx| {
12696        editor.set_text("one\ntwo\nthree\n", window, cx)
12697    });
12698    assert!(cx.read(|cx| editor.is_dirty(cx)));
12699
12700    let save = editor
12701        .update_in(cx, |editor, window, cx| {
12702            editor.save(
12703                SaveOptions {
12704                    format: true,
12705                    autosave: false,
12706                },
12707                project.clone(),
12708                window,
12709                cx,
12710            )
12711        })
12712        .unwrap();
12713    fake_server
12714        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12715            assert_eq!(
12716                params.text_document.uri,
12717                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12718            );
12719            assert_eq!(params.options.tab_size, 4);
12720            Ok(Some(vec![lsp::TextEdit::new(
12721                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12722                ", ".to_string(),
12723            )]))
12724        })
12725        .next()
12726        .await;
12727    save.await;
12728    assert_eq!(
12729        editor.update(cx, |editor, cx| editor.text(cx)),
12730        "one, two\nthree\n"
12731    );
12732    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12733}
12734
12735#[gpui::test]
12736async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12737    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12738
12739    editor.update_in(cx, |editor, window, cx| {
12740        editor.set_text("one\ntwo\nthree\n", window, cx)
12741    });
12742    assert!(cx.read(|cx| editor.is_dirty(cx)));
12743
12744    // Test that save still works when formatting hangs
12745    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12746        move |params, _| async move {
12747            assert_eq!(
12748                params.text_document.uri,
12749                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12750            );
12751            futures::future::pending::<()>().await;
12752            unreachable!()
12753        },
12754    );
12755    let save = editor
12756        .update_in(cx, |editor, window, cx| {
12757            editor.save(
12758                SaveOptions {
12759                    format: true,
12760                    autosave: false,
12761                },
12762                project.clone(),
12763                window,
12764                cx,
12765            )
12766        })
12767        .unwrap();
12768    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12769    save.await;
12770    assert_eq!(
12771        editor.update(cx, |editor, cx| editor.text(cx)),
12772        "one\ntwo\nthree\n"
12773    );
12774    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12775}
12776
12777#[gpui::test]
12778async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12779    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12780
12781    // Buffer starts clean, no formatting should be requested
12782    let save = editor
12783        .update_in(cx, |editor, window, cx| {
12784            editor.save(
12785                SaveOptions {
12786                    format: false,
12787                    autosave: false,
12788                },
12789                project.clone(),
12790                window,
12791                cx,
12792            )
12793        })
12794        .unwrap();
12795    let _pending_format_request = fake_server
12796        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12797            panic!("Should not be invoked");
12798        })
12799        .next();
12800    save.await;
12801    cx.run_until_parked();
12802}
12803
12804#[gpui::test]
12805async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12806    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12807
12808    // Set Rust language override and assert overridden tabsize is sent to language server
12809    update_test_language_settings(cx, |settings| {
12810        settings.languages.0.insert(
12811            "Rust".into(),
12812            LanguageSettingsContent {
12813                tab_size: NonZeroU32::new(8),
12814                ..Default::default()
12815            },
12816        );
12817    });
12818
12819    editor.update_in(cx, |editor, window, cx| {
12820        editor.set_text("something_new\n", window, cx)
12821    });
12822    assert!(cx.read(|cx| editor.is_dirty(cx)));
12823    let save = editor
12824        .update_in(cx, |editor, window, cx| {
12825            editor.save(
12826                SaveOptions {
12827                    format: true,
12828                    autosave: false,
12829                },
12830                project.clone(),
12831                window,
12832                cx,
12833            )
12834        })
12835        .unwrap();
12836    fake_server
12837        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12838            assert_eq!(
12839                params.text_document.uri,
12840                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12841            );
12842            assert_eq!(params.options.tab_size, 8);
12843            Ok(Some(Vec::new()))
12844        })
12845        .next()
12846        .await;
12847    save.await;
12848}
12849
12850#[gpui::test]
12851async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12852    init_test(cx, |settings| {
12853        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12854            settings::LanguageServerFormatterSpecifier::Current,
12855        )))
12856    });
12857
12858    let fs = FakeFs::new(cx.executor());
12859    fs.insert_file(path!("/file.rs"), Default::default()).await;
12860
12861    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12862
12863    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12864    language_registry.add(Arc::new(Language::new(
12865        LanguageConfig {
12866            name: "Rust".into(),
12867            matcher: LanguageMatcher {
12868                path_suffixes: vec!["rs".to_string()],
12869                ..Default::default()
12870            },
12871            ..LanguageConfig::default()
12872        },
12873        Some(tree_sitter_rust::LANGUAGE.into()),
12874    )));
12875    update_test_language_settings(cx, |settings| {
12876        // Enable Prettier formatting for the same buffer, and ensure
12877        // LSP is called instead of Prettier.
12878        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12879    });
12880    let mut fake_servers = language_registry.register_fake_lsp(
12881        "Rust",
12882        FakeLspAdapter {
12883            capabilities: lsp::ServerCapabilities {
12884                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12885                ..Default::default()
12886            },
12887            ..Default::default()
12888        },
12889    );
12890
12891    let buffer = project
12892        .update(cx, |project, cx| {
12893            project.open_local_buffer(path!("/file.rs"), cx)
12894        })
12895        .await
12896        .unwrap();
12897
12898    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12899    let (editor, cx) = cx.add_window_view(|window, cx| {
12900        build_editor_with_project(project.clone(), buffer, window, cx)
12901    });
12902    editor.update_in(cx, |editor, window, cx| {
12903        editor.set_text("one\ntwo\nthree\n", window, cx)
12904    });
12905
12906    let fake_server = fake_servers.next().await.unwrap();
12907
12908    let format = editor
12909        .update_in(cx, |editor, window, cx| {
12910            editor.perform_format(
12911                project.clone(),
12912                FormatTrigger::Manual,
12913                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12914                window,
12915                cx,
12916            )
12917        })
12918        .unwrap();
12919    fake_server
12920        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12921            assert_eq!(
12922                params.text_document.uri,
12923                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12924            );
12925            assert_eq!(params.options.tab_size, 4);
12926            Ok(Some(vec![lsp::TextEdit::new(
12927                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12928                ", ".to_string(),
12929            )]))
12930        })
12931        .next()
12932        .await;
12933    format.await;
12934    assert_eq!(
12935        editor.update(cx, |editor, cx| editor.text(cx)),
12936        "one, two\nthree\n"
12937    );
12938
12939    editor.update_in(cx, |editor, window, cx| {
12940        editor.set_text("one\ntwo\nthree\n", window, cx)
12941    });
12942    // Ensure we don't lock if formatting hangs.
12943    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12944        move |params, _| async move {
12945            assert_eq!(
12946                params.text_document.uri,
12947                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12948            );
12949            futures::future::pending::<()>().await;
12950            unreachable!()
12951        },
12952    );
12953    let format = editor
12954        .update_in(cx, |editor, window, cx| {
12955            editor.perform_format(
12956                project,
12957                FormatTrigger::Manual,
12958                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12959                window,
12960                cx,
12961            )
12962        })
12963        .unwrap();
12964    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12965    format.await;
12966    assert_eq!(
12967        editor.update(cx, |editor, cx| editor.text(cx)),
12968        "one\ntwo\nthree\n"
12969    );
12970}
12971
12972#[gpui::test]
12973async fn test_multiple_formatters(cx: &mut TestAppContext) {
12974    init_test(cx, |settings| {
12975        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12976        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12977            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12978            Formatter::CodeAction("code-action-1".into()),
12979            Formatter::CodeAction("code-action-2".into()),
12980        ]))
12981    });
12982
12983    let fs = FakeFs::new(cx.executor());
12984    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12985        .await;
12986
12987    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12988    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12989    language_registry.add(rust_lang());
12990
12991    let mut fake_servers = language_registry.register_fake_lsp(
12992        "Rust",
12993        FakeLspAdapter {
12994            capabilities: lsp::ServerCapabilities {
12995                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12996                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12997                    commands: vec!["the-command-for-code-action-1".into()],
12998                    ..Default::default()
12999                }),
13000                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13001                ..Default::default()
13002            },
13003            ..Default::default()
13004        },
13005    );
13006
13007    let buffer = project
13008        .update(cx, |project, cx| {
13009            project.open_local_buffer(path!("/file.rs"), cx)
13010        })
13011        .await
13012        .unwrap();
13013
13014    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13015    let (editor, cx) = cx.add_window_view(|window, cx| {
13016        build_editor_with_project(project.clone(), buffer, window, cx)
13017    });
13018
13019    let fake_server = fake_servers.next().await.unwrap();
13020    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13021        move |_params, _| async move {
13022            Ok(Some(vec![lsp::TextEdit::new(
13023                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13024                "applied-formatting\n".to_string(),
13025            )]))
13026        },
13027    );
13028    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13029        move |params, _| async move {
13030            let requested_code_actions = params.context.only.expect("Expected code action request");
13031            assert_eq!(requested_code_actions.len(), 1);
13032
13033            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
13034            let code_action = match requested_code_actions[0].as_str() {
13035                "code-action-1" => lsp::CodeAction {
13036                    kind: Some("code-action-1".into()),
13037                    edit: Some(lsp::WorkspaceEdit::new(
13038                        [(
13039                            uri,
13040                            vec![lsp::TextEdit::new(
13041                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13042                                "applied-code-action-1-edit\n".to_string(),
13043                            )],
13044                        )]
13045                        .into_iter()
13046                        .collect(),
13047                    )),
13048                    command: Some(lsp::Command {
13049                        command: "the-command-for-code-action-1".into(),
13050                        ..Default::default()
13051                    }),
13052                    ..Default::default()
13053                },
13054                "code-action-2" => lsp::CodeAction {
13055                    kind: Some("code-action-2".into()),
13056                    edit: Some(lsp::WorkspaceEdit::new(
13057                        [(
13058                            uri,
13059                            vec![lsp::TextEdit::new(
13060                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13061                                "applied-code-action-2-edit\n".to_string(),
13062                            )],
13063                        )]
13064                        .into_iter()
13065                        .collect(),
13066                    )),
13067                    ..Default::default()
13068                },
13069                req => panic!("Unexpected code action request: {:?}", req),
13070            };
13071            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13072                code_action,
13073            )]))
13074        },
13075    );
13076
13077    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
13078        move |params, _| async move { Ok(params) }
13079    });
13080
13081    let command_lock = Arc::new(futures::lock::Mutex::new(()));
13082    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
13083        let fake = fake_server.clone();
13084        let lock = command_lock.clone();
13085        move |params, _| {
13086            assert_eq!(params.command, "the-command-for-code-action-1");
13087            let fake = fake.clone();
13088            let lock = lock.clone();
13089            async move {
13090                lock.lock().await;
13091                fake.server
13092                    .request::<lsp::request::ApplyWorkspaceEdit>(
13093                        lsp::ApplyWorkspaceEditParams {
13094                            label: None,
13095                            edit: lsp::WorkspaceEdit {
13096                                changes: Some(
13097                                    [(
13098                                        lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
13099                                        vec![lsp::TextEdit {
13100                                            range: lsp::Range::new(
13101                                                lsp::Position::new(0, 0),
13102                                                lsp::Position::new(0, 0),
13103                                            ),
13104                                            new_text: "applied-code-action-1-command\n".into(),
13105                                        }],
13106                                    )]
13107                                    .into_iter()
13108                                    .collect(),
13109                                ),
13110                                ..Default::default()
13111                            },
13112                        },
13113                        DEFAULT_LSP_REQUEST_TIMEOUT,
13114                    )
13115                    .await
13116                    .into_response()
13117                    .unwrap();
13118                Ok(Some(json!(null)))
13119            }
13120        }
13121    });
13122
13123    editor
13124        .update_in(cx, |editor, window, cx| {
13125            editor.perform_format(
13126                project.clone(),
13127                FormatTrigger::Manual,
13128                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13129                window,
13130                cx,
13131            )
13132        })
13133        .unwrap()
13134        .await;
13135    editor.update(cx, |editor, cx| {
13136        assert_eq!(
13137            editor.text(cx),
13138            r#"
13139                applied-code-action-2-edit
13140                applied-code-action-1-command
13141                applied-code-action-1-edit
13142                applied-formatting
13143                one
13144                two
13145                three
13146            "#
13147            .unindent()
13148        );
13149    });
13150
13151    editor.update_in(cx, |editor, window, cx| {
13152        editor.undo(&Default::default(), window, cx);
13153        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13154    });
13155
13156    // Perform a manual edit while waiting for an LSP command
13157    // that's being run as part of a formatting code action.
13158    let lock_guard = command_lock.lock().await;
13159    let format = editor
13160        .update_in(cx, |editor, window, cx| {
13161            editor.perform_format(
13162                project.clone(),
13163                FormatTrigger::Manual,
13164                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13165                window,
13166                cx,
13167            )
13168        })
13169        .unwrap();
13170    cx.run_until_parked();
13171    editor.update(cx, |editor, cx| {
13172        assert_eq!(
13173            editor.text(cx),
13174            r#"
13175                applied-code-action-1-edit
13176                applied-formatting
13177                one
13178                two
13179                three
13180            "#
13181            .unindent()
13182        );
13183
13184        editor.buffer.update(cx, |buffer, cx| {
13185            let ix = buffer.len(cx);
13186            buffer.edit([(ix..ix, "edited\n")], None, cx);
13187        });
13188    });
13189
13190    // Allow the LSP command to proceed. Because the buffer was edited,
13191    // the second code action will not be run.
13192    drop(lock_guard);
13193    format.await;
13194    editor.update_in(cx, |editor, window, cx| {
13195        assert_eq!(
13196            editor.text(cx),
13197            r#"
13198                applied-code-action-1-command
13199                applied-code-action-1-edit
13200                applied-formatting
13201                one
13202                two
13203                three
13204                edited
13205            "#
13206            .unindent()
13207        );
13208
13209        // The manual edit is undone first, because it is the last thing the user did
13210        // (even though the command completed afterwards).
13211        editor.undo(&Default::default(), window, cx);
13212        assert_eq!(
13213            editor.text(cx),
13214            r#"
13215                applied-code-action-1-command
13216                applied-code-action-1-edit
13217                applied-formatting
13218                one
13219                two
13220                three
13221            "#
13222            .unindent()
13223        );
13224
13225        // All the formatting (including the command, which completed after the manual edit)
13226        // is undone together.
13227        editor.undo(&Default::default(), window, cx);
13228        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13229    });
13230}
13231
13232#[gpui::test]
13233async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
13234    init_test(cx, |settings| {
13235        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
13236            settings::LanguageServerFormatterSpecifier::Current,
13237        )]))
13238    });
13239
13240    let fs = FakeFs::new(cx.executor());
13241    fs.insert_file(path!("/file.ts"), Default::default()).await;
13242
13243    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13244
13245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13246    language_registry.add(Arc::new(Language::new(
13247        LanguageConfig {
13248            name: "TypeScript".into(),
13249            matcher: LanguageMatcher {
13250                path_suffixes: vec!["ts".to_string()],
13251                ..Default::default()
13252            },
13253            ..LanguageConfig::default()
13254        },
13255        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13256    )));
13257    update_test_language_settings(cx, |settings| {
13258        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13259    });
13260    let mut fake_servers = language_registry.register_fake_lsp(
13261        "TypeScript",
13262        FakeLspAdapter {
13263            capabilities: lsp::ServerCapabilities {
13264                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13265                ..Default::default()
13266            },
13267            ..Default::default()
13268        },
13269    );
13270
13271    let buffer = project
13272        .update(cx, |project, cx| {
13273            project.open_local_buffer(path!("/file.ts"), cx)
13274        })
13275        .await
13276        .unwrap();
13277
13278    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13279    let (editor, cx) = cx.add_window_view(|window, cx| {
13280        build_editor_with_project(project.clone(), buffer, window, cx)
13281    });
13282    editor.update_in(cx, |editor, window, cx| {
13283        editor.set_text(
13284            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13285            window,
13286            cx,
13287        )
13288    });
13289
13290    let fake_server = fake_servers.next().await.unwrap();
13291
13292    let format = editor
13293        .update_in(cx, |editor, window, cx| {
13294            editor.perform_code_action_kind(
13295                project.clone(),
13296                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13297                window,
13298                cx,
13299            )
13300        })
13301        .unwrap();
13302    fake_server
13303        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13304            assert_eq!(
13305                params.text_document.uri,
13306                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13307            );
13308            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13309                lsp::CodeAction {
13310                    title: "Organize Imports".to_string(),
13311                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13312                    edit: Some(lsp::WorkspaceEdit {
13313                        changes: Some(
13314                            [(
13315                                params.text_document.uri.clone(),
13316                                vec![lsp::TextEdit::new(
13317                                    lsp::Range::new(
13318                                        lsp::Position::new(1, 0),
13319                                        lsp::Position::new(2, 0),
13320                                    ),
13321                                    "".to_string(),
13322                                )],
13323                            )]
13324                            .into_iter()
13325                            .collect(),
13326                        ),
13327                        ..Default::default()
13328                    }),
13329                    ..Default::default()
13330                },
13331            )]))
13332        })
13333        .next()
13334        .await;
13335    format.await;
13336    assert_eq!(
13337        editor.update(cx, |editor, cx| editor.text(cx)),
13338        "import { a } from 'module';\n\nconst x = a;\n"
13339    );
13340
13341    editor.update_in(cx, |editor, window, cx| {
13342        editor.set_text(
13343            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13344            window,
13345            cx,
13346        )
13347    });
13348    // Ensure we don't lock if code action hangs.
13349    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13350        move |params, _| async move {
13351            assert_eq!(
13352                params.text_document.uri,
13353                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13354            );
13355            futures::future::pending::<()>().await;
13356            unreachable!()
13357        },
13358    );
13359    let format = editor
13360        .update_in(cx, |editor, window, cx| {
13361            editor.perform_code_action_kind(
13362                project,
13363                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13364                window,
13365                cx,
13366            )
13367        })
13368        .unwrap();
13369    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13370    format.await;
13371    assert_eq!(
13372        editor.update(cx, |editor, cx| editor.text(cx)),
13373        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13374    );
13375}
13376
13377#[gpui::test]
13378async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13379    init_test(cx, |_| {});
13380
13381    let mut cx = EditorLspTestContext::new_rust(
13382        lsp::ServerCapabilities {
13383            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13384            ..Default::default()
13385        },
13386        cx,
13387    )
13388    .await;
13389
13390    cx.set_state(indoc! {"
13391        one.twoˇ
13392    "});
13393
13394    // The format request takes a long time. When it completes, it inserts
13395    // a newline and an indent before the `.`
13396    cx.lsp
13397        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13398            let executor = cx.background_executor().clone();
13399            async move {
13400                executor.timer(Duration::from_millis(100)).await;
13401                Ok(Some(vec![lsp::TextEdit {
13402                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13403                    new_text: "\n    ".into(),
13404                }]))
13405            }
13406        });
13407
13408    // Submit a format request.
13409    let format_1 = cx
13410        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13411        .unwrap();
13412    cx.executor().run_until_parked();
13413
13414    // Submit a second format request.
13415    let format_2 = cx
13416        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13417        .unwrap();
13418    cx.executor().run_until_parked();
13419
13420    // Wait for both format requests to complete
13421    cx.executor().advance_clock(Duration::from_millis(200));
13422    format_1.await.unwrap();
13423    format_2.await.unwrap();
13424
13425    // The formatting edits only happens once.
13426    cx.assert_editor_state(indoc! {"
13427        one
13428            .twoˇ
13429    "});
13430}
13431
13432#[gpui::test]
13433async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13434    init_test(cx, |settings| {
13435        settings.defaults.formatter = Some(FormatterList::default())
13436    });
13437
13438    let mut cx = EditorLspTestContext::new_rust(
13439        lsp::ServerCapabilities {
13440            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13441            ..Default::default()
13442        },
13443        cx,
13444    )
13445    .await;
13446
13447    // Record which buffer changes have been sent to the language server
13448    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13449    cx.lsp
13450        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13451            let buffer_changes = buffer_changes.clone();
13452            move |params, _| {
13453                buffer_changes.lock().extend(
13454                    params
13455                        .content_changes
13456                        .into_iter()
13457                        .map(|e| (e.range.unwrap(), e.text)),
13458                );
13459            }
13460        });
13461    // Handle formatting requests to the language server.
13462    cx.lsp
13463        .set_request_handler::<lsp::request::Formatting, _, _>({
13464            move |_, _| {
13465                // Insert blank lines between each line of the buffer.
13466                async move {
13467                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13468                    // DidChangedTextDocument to the LSP before sending the formatting request.
13469                    // assert_eq!(
13470                    //     &buffer_changes.lock()[1..],
13471                    //     &[
13472                    //         (
13473                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13474                    //             "".into()
13475                    //         ),
13476                    //         (
13477                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13478                    //             "".into()
13479                    //         ),
13480                    //         (
13481                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13482                    //             "\n".into()
13483                    //         ),
13484                    //     ]
13485                    // );
13486
13487                    Ok(Some(vec![
13488                        lsp::TextEdit {
13489                            range: lsp::Range::new(
13490                                lsp::Position::new(1, 0),
13491                                lsp::Position::new(1, 0),
13492                            ),
13493                            new_text: "\n".into(),
13494                        },
13495                        lsp::TextEdit {
13496                            range: lsp::Range::new(
13497                                lsp::Position::new(2, 0),
13498                                lsp::Position::new(2, 0),
13499                            ),
13500                            new_text: "\n".into(),
13501                        },
13502                    ]))
13503                }
13504            }
13505        });
13506
13507    // Set up a buffer white some trailing whitespace and no trailing newline.
13508    cx.set_state(
13509        &[
13510            "one ",   //
13511            "twoˇ",   //
13512            "three ", //
13513            "four",   //
13514        ]
13515        .join("\n"),
13516    );
13517
13518    // Submit a format request.
13519    let format = cx
13520        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13521        .unwrap();
13522
13523    cx.run_until_parked();
13524    // After formatting the buffer, the trailing whitespace is stripped,
13525    // a newline is appended, and the edits provided by the language server
13526    // have been applied.
13527    format.await.unwrap();
13528
13529    cx.assert_editor_state(
13530        &[
13531            "one",   //
13532            "",      //
13533            "twoˇ",  //
13534            "",      //
13535            "three", //
13536            "four",  //
13537            "",      //
13538        ]
13539        .join("\n"),
13540    );
13541
13542    // Undoing the formatting undoes the trailing whitespace removal, the
13543    // trailing newline, and the LSP edits.
13544    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13545    cx.assert_editor_state(
13546        &[
13547            "one ",   //
13548            "twoˇ",   //
13549            "three ", //
13550            "four",   //
13551        ]
13552        .join("\n"),
13553    );
13554}
13555
13556#[gpui::test]
13557async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13558    cx: &mut TestAppContext,
13559) {
13560    init_test(cx, |_| {});
13561
13562    cx.update(|cx| {
13563        cx.update_global::<SettingsStore, _>(|settings, cx| {
13564            settings.update_user_settings(cx, |settings| {
13565                settings.editor.auto_signature_help = Some(true);
13566                settings.editor.hover_popover_delay = Some(DelayMs(300));
13567            });
13568        });
13569    });
13570
13571    let mut cx = EditorLspTestContext::new_rust(
13572        lsp::ServerCapabilities {
13573            signature_help_provider: Some(lsp::SignatureHelpOptions {
13574                ..Default::default()
13575            }),
13576            ..Default::default()
13577        },
13578        cx,
13579    )
13580    .await;
13581
13582    let language = Language::new(
13583        LanguageConfig {
13584            name: "Rust".into(),
13585            brackets: BracketPairConfig {
13586                pairs: vec![
13587                    BracketPair {
13588                        start: "{".to_string(),
13589                        end: "}".to_string(),
13590                        close: true,
13591                        surround: true,
13592                        newline: true,
13593                    },
13594                    BracketPair {
13595                        start: "(".to_string(),
13596                        end: ")".to_string(),
13597                        close: true,
13598                        surround: true,
13599                        newline: true,
13600                    },
13601                    BracketPair {
13602                        start: "/*".to_string(),
13603                        end: " */".to_string(),
13604                        close: true,
13605                        surround: true,
13606                        newline: true,
13607                    },
13608                    BracketPair {
13609                        start: "[".to_string(),
13610                        end: "]".to_string(),
13611                        close: false,
13612                        surround: false,
13613                        newline: true,
13614                    },
13615                    BracketPair {
13616                        start: "\"".to_string(),
13617                        end: "\"".to_string(),
13618                        close: true,
13619                        surround: true,
13620                        newline: false,
13621                    },
13622                    BracketPair {
13623                        start: "<".to_string(),
13624                        end: ">".to_string(),
13625                        close: false,
13626                        surround: true,
13627                        newline: true,
13628                    },
13629                ],
13630                ..Default::default()
13631            },
13632            autoclose_before: "})]".to_string(),
13633            ..Default::default()
13634        },
13635        Some(tree_sitter_rust::LANGUAGE.into()),
13636    );
13637    let language = Arc::new(language);
13638
13639    cx.language_registry().add(language.clone());
13640    cx.update_buffer(|buffer, cx| {
13641        buffer.set_language(Some(language), cx);
13642    });
13643
13644    cx.set_state(
13645        &r#"
13646            fn main() {
13647                sampleˇ
13648            }
13649        "#
13650        .unindent(),
13651    );
13652
13653    cx.update_editor(|editor, window, cx| {
13654        editor.handle_input("(", window, cx);
13655    });
13656    cx.assert_editor_state(
13657        &"
13658            fn main() {
13659                sample(ˇ)
13660            }
13661        "
13662        .unindent(),
13663    );
13664
13665    let mocked_response = lsp::SignatureHelp {
13666        signatures: vec![lsp::SignatureInformation {
13667            label: "fn sample(param1: u8, param2: u8)".to_string(),
13668            documentation: None,
13669            parameters: Some(vec![
13670                lsp::ParameterInformation {
13671                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13672                    documentation: None,
13673                },
13674                lsp::ParameterInformation {
13675                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13676                    documentation: None,
13677                },
13678            ]),
13679            active_parameter: None,
13680        }],
13681        active_signature: Some(0),
13682        active_parameter: Some(0),
13683    };
13684    handle_signature_help_request(&mut cx, mocked_response).await;
13685
13686    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13687        .await;
13688
13689    cx.editor(|editor, _, _| {
13690        let signature_help_state = editor.signature_help_state.popover().cloned();
13691        let signature = signature_help_state.unwrap();
13692        assert_eq!(
13693            signature.signatures[signature.current_signature].label,
13694            "fn sample(param1: u8, param2: u8)"
13695        );
13696    });
13697}
13698
13699#[gpui::test]
13700async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
13701    init_test(cx, |_| {});
13702
13703    let delay_ms = 500;
13704    cx.update(|cx| {
13705        cx.update_global::<SettingsStore, _>(|settings, cx| {
13706            settings.update_user_settings(cx, |settings| {
13707                settings.editor.auto_signature_help = Some(true);
13708                settings.editor.show_signature_help_after_edits = Some(false);
13709                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
13710            });
13711        });
13712    });
13713
13714    let mut cx = EditorLspTestContext::new_rust(
13715        lsp::ServerCapabilities {
13716            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13717            ..lsp::ServerCapabilities::default()
13718        },
13719        cx,
13720    )
13721    .await;
13722
13723    let mocked_response = lsp::SignatureHelp {
13724        signatures: vec![lsp::SignatureInformation {
13725            label: "fn sample(param1: u8)".to_string(),
13726            documentation: None,
13727            parameters: Some(vec![lsp::ParameterInformation {
13728                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13729                documentation: None,
13730            }]),
13731            active_parameter: None,
13732        }],
13733        active_signature: Some(0),
13734        active_parameter: Some(0),
13735    };
13736
13737    cx.set_state(indoc! {"
13738        fn main() {
13739            sample(ˇ);
13740        }
13741
13742        fn sample(param1: u8) {}
13743    "});
13744
13745    // Manual trigger should show immediately without delay
13746    cx.update_editor(|editor, window, cx| {
13747        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13748    });
13749    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13750    cx.run_until_parked();
13751    cx.editor(|editor, _, _| {
13752        assert!(
13753            editor.signature_help_state.is_shown(),
13754            "Manual trigger should show signature help without delay"
13755        );
13756    });
13757
13758    cx.update_editor(|editor, _, cx| {
13759        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13760    });
13761    cx.run_until_parked();
13762    cx.editor(|editor, _, _| {
13763        assert!(!editor.signature_help_state.is_shown());
13764    });
13765
13766    // Auto trigger (cursor movement into brackets) should respect delay
13767    cx.set_state(indoc! {"
13768        fn main() {
13769            sampleˇ();
13770        }
13771
13772        fn sample(param1: u8) {}
13773    "});
13774    cx.update_editor(|editor, window, cx| {
13775        editor.move_right(&MoveRight, window, cx);
13776    });
13777    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13778    cx.run_until_parked();
13779    cx.editor(|editor, _, _| {
13780        assert!(
13781            !editor.signature_help_state.is_shown(),
13782            "Auto trigger should wait for delay before showing signature help"
13783        );
13784    });
13785
13786    cx.executor()
13787        .advance_clock(Duration::from_millis(delay_ms + 50));
13788    cx.run_until_parked();
13789    cx.editor(|editor, _, _| {
13790        assert!(
13791            editor.signature_help_state.is_shown(),
13792            "Auto trigger should show signature help after delay elapsed"
13793        );
13794    });
13795}
13796
13797#[gpui::test]
13798async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
13799    init_test(cx, |_| {});
13800
13801    let delay_ms = 500;
13802    cx.update(|cx| {
13803        cx.update_global::<SettingsStore, _>(|settings, cx| {
13804            settings.update_user_settings(cx, |settings| {
13805                settings.editor.auto_signature_help = Some(false);
13806                settings.editor.show_signature_help_after_edits = Some(true);
13807                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
13808            });
13809        });
13810    });
13811
13812    let mut cx = EditorLspTestContext::new_rust(
13813        lsp::ServerCapabilities {
13814            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13815            ..lsp::ServerCapabilities::default()
13816        },
13817        cx,
13818    )
13819    .await;
13820
13821    let language = Arc::new(Language::new(
13822        LanguageConfig {
13823            name: "Rust".into(),
13824            brackets: BracketPairConfig {
13825                pairs: vec![BracketPair {
13826                    start: "(".to_string(),
13827                    end: ")".to_string(),
13828                    close: true,
13829                    surround: true,
13830                    newline: true,
13831                }],
13832                ..BracketPairConfig::default()
13833            },
13834            autoclose_before: "})".to_string(),
13835            ..LanguageConfig::default()
13836        },
13837        Some(tree_sitter_rust::LANGUAGE.into()),
13838    ));
13839    cx.language_registry().add(language.clone());
13840    cx.update_buffer(|buffer, cx| {
13841        buffer.set_language(Some(language), cx);
13842    });
13843
13844    let mocked_response = lsp::SignatureHelp {
13845        signatures: vec![lsp::SignatureInformation {
13846            label: "fn sample(param1: u8)".to_string(),
13847            documentation: None,
13848            parameters: Some(vec![lsp::ParameterInformation {
13849                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13850                documentation: None,
13851            }]),
13852            active_parameter: None,
13853        }],
13854        active_signature: Some(0),
13855        active_parameter: Some(0),
13856    };
13857
13858    cx.set_state(indoc! {"
13859        fn main() {
13860            sampleˇ
13861        }
13862    "});
13863
13864    // Typing bracket should show signature help immediately without delay
13865    cx.update_editor(|editor, window, cx| {
13866        editor.handle_input("(", window, cx);
13867    });
13868    handle_signature_help_request(&mut cx, mocked_response).await;
13869    cx.run_until_parked();
13870    cx.editor(|editor, _, _| {
13871        assert!(
13872            editor.signature_help_state.is_shown(),
13873            "show_signature_help_after_edits should show signature help without delay"
13874        );
13875    });
13876}
13877
13878#[gpui::test]
13879async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13880    init_test(cx, |_| {});
13881
13882    cx.update(|cx| {
13883        cx.update_global::<SettingsStore, _>(|settings, cx| {
13884            settings.update_user_settings(cx, |settings| {
13885                settings.editor.auto_signature_help = Some(false);
13886                settings.editor.show_signature_help_after_edits = Some(false);
13887            });
13888        });
13889    });
13890
13891    let mut cx = EditorLspTestContext::new_rust(
13892        lsp::ServerCapabilities {
13893            signature_help_provider: Some(lsp::SignatureHelpOptions {
13894                ..Default::default()
13895            }),
13896            ..Default::default()
13897        },
13898        cx,
13899    )
13900    .await;
13901
13902    let language = Language::new(
13903        LanguageConfig {
13904            name: "Rust".into(),
13905            brackets: BracketPairConfig {
13906                pairs: vec![
13907                    BracketPair {
13908                        start: "{".to_string(),
13909                        end: "}".to_string(),
13910                        close: true,
13911                        surround: true,
13912                        newline: true,
13913                    },
13914                    BracketPair {
13915                        start: "(".to_string(),
13916                        end: ")".to_string(),
13917                        close: true,
13918                        surround: true,
13919                        newline: true,
13920                    },
13921                    BracketPair {
13922                        start: "/*".to_string(),
13923                        end: " */".to_string(),
13924                        close: true,
13925                        surround: true,
13926                        newline: true,
13927                    },
13928                    BracketPair {
13929                        start: "[".to_string(),
13930                        end: "]".to_string(),
13931                        close: false,
13932                        surround: false,
13933                        newline: true,
13934                    },
13935                    BracketPair {
13936                        start: "\"".to_string(),
13937                        end: "\"".to_string(),
13938                        close: true,
13939                        surround: true,
13940                        newline: false,
13941                    },
13942                    BracketPair {
13943                        start: "<".to_string(),
13944                        end: ">".to_string(),
13945                        close: false,
13946                        surround: true,
13947                        newline: true,
13948                    },
13949                ],
13950                ..Default::default()
13951            },
13952            autoclose_before: "})]".to_string(),
13953            ..Default::default()
13954        },
13955        Some(tree_sitter_rust::LANGUAGE.into()),
13956    );
13957    let language = Arc::new(language);
13958
13959    cx.language_registry().add(language.clone());
13960    cx.update_buffer(|buffer, cx| {
13961        buffer.set_language(Some(language), cx);
13962    });
13963
13964    // Ensure that signature_help is not called when no signature help is enabled.
13965    cx.set_state(
13966        &r#"
13967            fn main() {
13968                sampleˇ
13969            }
13970        "#
13971        .unindent(),
13972    );
13973    cx.update_editor(|editor, window, cx| {
13974        editor.handle_input("(", window, cx);
13975    });
13976    cx.assert_editor_state(
13977        &"
13978            fn main() {
13979                sample(ˇ)
13980            }
13981        "
13982        .unindent(),
13983    );
13984    cx.editor(|editor, _, _| {
13985        assert!(editor.signature_help_state.task().is_none());
13986    });
13987
13988    let mocked_response = lsp::SignatureHelp {
13989        signatures: vec![lsp::SignatureInformation {
13990            label: "fn sample(param1: u8, param2: u8)".to_string(),
13991            documentation: None,
13992            parameters: Some(vec![
13993                lsp::ParameterInformation {
13994                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13995                    documentation: None,
13996                },
13997                lsp::ParameterInformation {
13998                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13999                    documentation: None,
14000                },
14001            ]),
14002            active_parameter: None,
14003        }],
14004        active_signature: Some(0),
14005        active_parameter: Some(0),
14006    };
14007
14008    // Ensure that signature_help is called when enabled afte edits
14009    cx.update(|_, cx| {
14010        cx.update_global::<SettingsStore, _>(|settings, cx| {
14011            settings.update_user_settings(cx, |settings| {
14012                settings.editor.auto_signature_help = Some(false);
14013                settings.editor.show_signature_help_after_edits = Some(true);
14014            });
14015        });
14016    });
14017    cx.set_state(
14018        &r#"
14019            fn main() {
14020                sampleˇ
14021            }
14022        "#
14023        .unindent(),
14024    );
14025    cx.update_editor(|editor, window, cx| {
14026        editor.handle_input("(", window, cx);
14027    });
14028    cx.assert_editor_state(
14029        &"
14030            fn main() {
14031                sample(ˇ)
14032            }
14033        "
14034        .unindent(),
14035    );
14036    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14037    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14038        .await;
14039    cx.update_editor(|editor, _, _| {
14040        let signature_help_state = editor.signature_help_state.popover().cloned();
14041        assert!(signature_help_state.is_some());
14042        let signature = signature_help_state.unwrap();
14043        assert_eq!(
14044            signature.signatures[signature.current_signature].label,
14045            "fn sample(param1: u8, param2: u8)"
14046        );
14047        editor.signature_help_state = SignatureHelpState::default();
14048    });
14049
14050    // Ensure that signature_help is called when auto signature help override is enabled
14051    cx.update(|_, cx| {
14052        cx.update_global::<SettingsStore, _>(|settings, cx| {
14053            settings.update_user_settings(cx, |settings| {
14054                settings.editor.auto_signature_help = Some(true);
14055                settings.editor.show_signature_help_after_edits = Some(false);
14056            });
14057        });
14058    });
14059    cx.set_state(
14060        &r#"
14061            fn main() {
14062                sampleˇ
14063            }
14064        "#
14065        .unindent(),
14066    );
14067    cx.update_editor(|editor, window, cx| {
14068        editor.handle_input("(", window, cx);
14069    });
14070    cx.assert_editor_state(
14071        &"
14072            fn main() {
14073                sample(ˇ)
14074            }
14075        "
14076        .unindent(),
14077    );
14078    handle_signature_help_request(&mut cx, mocked_response).await;
14079    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14080        .await;
14081    cx.editor(|editor, _, _| {
14082        let signature_help_state = editor.signature_help_state.popover().cloned();
14083        assert!(signature_help_state.is_some());
14084        let signature = signature_help_state.unwrap();
14085        assert_eq!(
14086            signature.signatures[signature.current_signature].label,
14087            "fn sample(param1: u8, param2: u8)"
14088        );
14089    });
14090}
14091
14092#[gpui::test]
14093async fn test_signature_help(cx: &mut TestAppContext) {
14094    init_test(cx, |_| {});
14095    cx.update(|cx| {
14096        cx.update_global::<SettingsStore, _>(|settings, cx| {
14097            settings.update_user_settings(cx, |settings| {
14098                settings.editor.auto_signature_help = Some(true);
14099            });
14100        });
14101    });
14102
14103    let mut cx = EditorLspTestContext::new_rust(
14104        lsp::ServerCapabilities {
14105            signature_help_provider: Some(lsp::SignatureHelpOptions {
14106                ..Default::default()
14107            }),
14108            ..Default::default()
14109        },
14110        cx,
14111    )
14112    .await;
14113
14114    // A test that directly calls `show_signature_help`
14115    cx.update_editor(|editor, window, cx| {
14116        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14117    });
14118
14119    let mocked_response = lsp::SignatureHelp {
14120        signatures: vec![lsp::SignatureInformation {
14121            label: "fn sample(param1: u8, param2: u8)".to_string(),
14122            documentation: None,
14123            parameters: Some(vec![
14124                lsp::ParameterInformation {
14125                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14126                    documentation: None,
14127                },
14128                lsp::ParameterInformation {
14129                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14130                    documentation: None,
14131                },
14132            ]),
14133            active_parameter: None,
14134        }],
14135        active_signature: Some(0),
14136        active_parameter: Some(0),
14137    };
14138    handle_signature_help_request(&mut cx, mocked_response).await;
14139
14140    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14141        .await;
14142
14143    cx.editor(|editor, _, _| {
14144        let signature_help_state = editor.signature_help_state.popover().cloned();
14145        assert!(signature_help_state.is_some());
14146        let signature = signature_help_state.unwrap();
14147        assert_eq!(
14148            signature.signatures[signature.current_signature].label,
14149            "fn sample(param1: u8, param2: u8)"
14150        );
14151    });
14152
14153    // When exiting outside from inside the brackets, `signature_help` is closed.
14154    cx.set_state(indoc! {"
14155        fn main() {
14156            sample(ˇ);
14157        }
14158
14159        fn sample(param1: u8, param2: u8) {}
14160    "});
14161
14162    cx.update_editor(|editor, window, cx| {
14163        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14164            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
14165        });
14166    });
14167
14168    let mocked_response = lsp::SignatureHelp {
14169        signatures: Vec::new(),
14170        active_signature: None,
14171        active_parameter: None,
14172    };
14173    handle_signature_help_request(&mut cx, mocked_response).await;
14174
14175    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14176        .await;
14177
14178    cx.editor(|editor, _, _| {
14179        assert!(!editor.signature_help_state.is_shown());
14180    });
14181
14182    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
14183    cx.set_state(indoc! {"
14184        fn main() {
14185            sample(ˇ);
14186        }
14187
14188        fn sample(param1: u8, param2: u8) {}
14189    "});
14190
14191    let mocked_response = lsp::SignatureHelp {
14192        signatures: vec![lsp::SignatureInformation {
14193            label: "fn sample(param1: u8, param2: u8)".to_string(),
14194            documentation: None,
14195            parameters: Some(vec![
14196                lsp::ParameterInformation {
14197                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14198                    documentation: None,
14199                },
14200                lsp::ParameterInformation {
14201                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14202                    documentation: None,
14203                },
14204            ]),
14205            active_parameter: None,
14206        }],
14207        active_signature: Some(0),
14208        active_parameter: Some(0),
14209    };
14210    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14211    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14212        .await;
14213    cx.editor(|editor, _, _| {
14214        assert!(editor.signature_help_state.is_shown());
14215    });
14216
14217    // Restore the popover with more parameter input
14218    cx.set_state(indoc! {"
14219        fn main() {
14220            sample(param1, param2ˇ);
14221        }
14222
14223        fn sample(param1: u8, param2: u8) {}
14224    "});
14225
14226    let mocked_response = lsp::SignatureHelp {
14227        signatures: vec![lsp::SignatureInformation {
14228            label: "fn sample(param1: u8, param2: u8)".to_string(),
14229            documentation: None,
14230            parameters: Some(vec![
14231                lsp::ParameterInformation {
14232                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14233                    documentation: None,
14234                },
14235                lsp::ParameterInformation {
14236                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14237                    documentation: None,
14238                },
14239            ]),
14240            active_parameter: None,
14241        }],
14242        active_signature: Some(0),
14243        active_parameter: Some(1),
14244    };
14245    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14246    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14247        .await;
14248
14249    // When selecting a range, the popover is gone.
14250    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
14251    cx.update_editor(|editor, window, cx| {
14252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14253            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14254        })
14255    });
14256    cx.assert_editor_state(indoc! {"
14257        fn main() {
14258            sample(param1, «ˇparam2»);
14259        }
14260
14261        fn sample(param1: u8, param2: u8) {}
14262    "});
14263    cx.editor(|editor, _, _| {
14264        assert!(!editor.signature_help_state.is_shown());
14265    });
14266
14267    // When unselecting again, the popover is back if within the brackets.
14268    cx.update_editor(|editor, window, cx| {
14269        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14270            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14271        })
14272    });
14273    cx.assert_editor_state(indoc! {"
14274        fn main() {
14275            sample(param1, ˇparam2);
14276        }
14277
14278        fn sample(param1: u8, param2: u8) {}
14279    "});
14280    handle_signature_help_request(&mut cx, mocked_response).await;
14281    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14282        .await;
14283    cx.editor(|editor, _, _| {
14284        assert!(editor.signature_help_state.is_shown());
14285    });
14286
14287    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14288    cx.update_editor(|editor, window, cx| {
14289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14290            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14291            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14292        })
14293    });
14294    cx.assert_editor_state(indoc! {"
14295        fn main() {
14296            sample(param1, ˇparam2);
14297        }
14298
14299        fn sample(param1: u8, param2: u8) {}
14300    "});
14301
14302    let mocked_response = lsp::SignatureHelp {
14303        signatures: vec![lsp::SignatureInformation {
14304            label: "fn sample(param1: u8, param2: u8)".to_string(),
14305            documentation: None,
14306            parameters: Some(vec![
14307                lsp::ParameterInformation {
14308                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14309                    documentation: None,
14310                },
14311                lsp::ParameterInformation {
14312                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14313                    documentation: None,
14314                },
14315            ]),
14316            active_parameter: None,
14317        }],
14318        active_signature: Some(0),
14319        active_parameter: Some(1),
14320    };
14321    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14322    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14323        .await;
14324    cx.update_editor(|editor, _, cx| {
14325        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14326    });
14327    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14328        .await;
14329    cx.update_editor(|editor, window, cx| {
14330        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14331            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14332        })
14333    });
14334    cx.assert_editor_state(indoc! {"
14335        fn main() {
14336            sample(param1, «ˇparam2»);
14337        }
14338
14339        fn sample(param1: u8, param2: u8) {}
14340    "});
14341    cx.update_editor(|editor, window, cx| {
14342        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14343            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14344        })
14345    });
14346    cx.assert_editor_state(indoc! {"
14347        fn main() {
14348            sample(param1, ˇparam2);
14349        }
14350
14351        fn sample(param1: u8, param2: u8) {}
14352    "});
14353    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14354        .await;
14355}
14356
14357#[gpui::test]
14358async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14359    init_test(cx, |_| {});
14360
14361    let mut cx = EditorLspTestContext::new_rust(
14362        lsp::ServerCapabilities {
14363            signature_help_provider: Some(lsp::SignatureHelpOptions {
14364                ..Default::default()
14365            }),
14366            ..Default::default()
14367        },
14368        cx,
14369    )
14370    .await;
14371
14372    cx.set_state(indoc! {"
14373        fn main() {
14374            overloadedˇ
14375        }
14376    "});
14377
14378    cx.update_editor(|editor, window, cx| {
14379        editor.handle_input("(", window, cx);
14380        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14381    });
14382
14383    // Mock response with 3 signatures
14384    let mocked_response = lsp::SignatureHelp {
14385        signatures: vec![
14386            lsp::SignatureInformation {
14387                label: "fn overloaded(x: i32)".to_string(),
14388                documentation: None,
14389                parameters: Some(vec![lsp::ParameterInformation {
14390                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14391                    documentation: None,
14392                }]),
14393                active_parameter: None,
14394            },
14395            lsp::SignatureInformation {
14396                label: "fn overloaded(x: i32, y: i32)".to_string(),
14397                documentation: None,
14398                parameters: Some(vec![
14399                    lsp::ParameterInformation {
14400                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14401                        documentation: None,
14402                    },
14403                    lsp::ParameterInformation {
14404                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14405                        documentation: None,
14406                    },
14407                ]),
14408                active_parameter: None,
14409            },
14410            lsp::SignatureInformation {
14411                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14412                documentation: None,
14413                parameters: Some(vec![
14414                    lsp::ParameterInformation {
14415                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14416                        documentation: None,
14417                    },
14418                    lsp::ParameterInformation {
14419                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14420                        documentation: None,
14421                    },
14422                    lsp::ParameterInformation {
14423                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14424                        documentation: None,
14425                    },
14426                ]),
14427                active_parameter: None,
14428            },
14429        ],
14430        active_signature: Some(1),
14431        active_parameter: Some(0),
14432    };
14433    handle_signature_help_request(&mut cx, mocked_response).await;
14434
14435    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14436        .await;
14437
14438    // Verify we have multiple signatures and the right one is selected
14439    cx.editor(|editor, _, _| {
14440        let popover = editor.signature_help_state.popover().cloned().unwrap();
14441        assert_eq!(popover.signatures.len(), 3);
14442        // active_signature was 1, so that should be the current
14443        assert_eq!(popover.current_signature, 1);
14444        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14445        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14446        assert_eq!(
14447            popover.signatures[2].label,
14448            "fn overloaded(x: i32, y: i32, z: i32)"
14449        );
14450    });
14451
14452    // Test navigation functionality
14453    cx.update_editor(|editor, window, cx| {
14454        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14455    });
14456
14457    cx.editor(|editor, _, _| {
14458        let popover = editor.signature_help_state.popover().cloned().unwrap();
14459        assert_eq!(popover.current_signature, 2);
14460    });
14461
14462    // Test wrap around
14463    cx.update_editor(|editor, window, cx| {
14464        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14465    });
14466
14467    cx.editor(|editor, _, _| {
14468        let popover = editor.signature_help_state.popover().cloned().unwrap();
14469        assert_eq!(popover.current_signature, 0);
14470    });
14471
14472    // Test previous navigation
14473    cx.update_editor(|editor, window, cx| {
14474        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14475    });
14476
14477    cx.editor(|editor, _, _| {
14478        let popover = editor.signature_help_state.popover().cloned().unwrap();
14479        assert_eq!(popover.current_signature, 2);
14480    });
14481}
14482
14483#[gpui::test]
14484async fn test_completion_mode(cx: &mut TestAppContext) {
14485    init_test(cx, |_| {});
14486    let mut cx = EditorLspTestContext::new_rust(
14487        lsp::ServerCapabilities {
14488            completion_provider: Some(lsp::CompletionOptions {
14489                resolve_provider: Some(true),
14490                ..Default::default()
14491            }),
14492            ..Default::default()
14493        },
14494        cx,
14495    )
14496    .await;
14497
14498    struct Run {
14499        run_description: &'static str,
14500        initial_state: String,
14501        buffer_marked_text: String,
14502        completion_label: &'static str,
14503        completion_text: &'static str,
14504        expected_with_insert_mode: String,
14505        expected_with_replace_mode: String,
14506        expected_with_replace_subsequence_mode: String,
14507        expected_with_replace_suffix_mode: String,
14508    }
14509
14510    let runs = [
14511        Run {
14512            run_description: "Start of word matches completion text",
14513            initial_state: "before ediˇ after".into(),
14514            buffer_marked_text: "before <edi|> after".into(),
14515            completion_label: "editor",
14516            completion_text: "editor",
14517            expected_with_insert_mode: "before editorˇ after".into(),
14518            expected_with_replace_mode: "before editorˇ after".into(),
14519            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14520            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14521        },
14522        Run {
14523            run_description: "Accept same text at the middle of the word",
14524            initial_state: "before ediˇtor after".into(),
14525            buffer_marked_text: "before <edi|tor> after".into(),
14526            completion_label: "editor",
14527            completion_text: "editor",
14528            expected_with_insert_mode: "before editorˇtor after".into(),
14529            expected_with_replace_mode: "before editorˇ after".into(),
14530            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14531            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14532        },
14533        Run {
14534            run_description: "End of word matches completion text -- cursor at end",
14535            initial_state: "before torˇ after".into(),
14536            buffer_marked_text: "before <tor|> after".into(),
14537            completion_label: "editor",
14538            completion_text: "editor",
14539            expected_with_insert_mode: "before editorˇ after".into(),
14540            expected_with_replace_mode: "before editorˇ after".into(),
14541            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14542            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14543        },
14544        Run {
14545            run_description: "End of word matches completion text -- cursor at start",
14546            initial_state: "before ˇtor after".into(),
14547            buffer_marked_text: "before <|tor> after".into(),
14548            completion_label: "editor",
14549            completion_text: "editor",
14550            expected_with_insert_mode: "before editorˇtor after".into(),
14551            expected_with_replace_mode: "before editorˇ after".into(),
14552            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14553            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14554        },
14555        Run {
14556            run_description: "Prepend text containing whitespace",
14557            initial_state: "pˇfield: bool".into(),
14558            buffer_marked_text: "<p|field>: bool".into(),
14559            completion_label: "pub ",
14560            completion_text: "pub ",
14561            expected_with_insert_mode: "pub ˇfield: bool".into(),
14562            expected_with_replace_mode: "pub ˇ: bool".into(),
14563            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14564            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14565        },
14566        Run {
14567            run_description: "Add element to start of list",
14568            initial_state: "[element_ˇelement_2]".into(),
14569            buffer_marked_text: "[<element_|element_2>]".into(),
14570            completion_label: "element_1",
14571            completion_text: "element_1",
14572            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14573            expected_with_replace_mode: "[element_1ˇ]".into(),
14574            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14575            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14576        },
14577        Run {
14578            run_description: "Add element to start of list -- first and second elements are equal",
14579            initial_state: "[elˇelement]".into(),
14580            buffer_marked_text: "[<el|element>]".into(),
14581            completion_label: "element",
14582            completion_text: "element",
14583            expected_with_insert_mode: "[elementˇelement]".into(),
14584            expected_with_replace_mode: "[elementˇ]".into(),
14585            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14586            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14587        },
14588        Run {
14589            run_description: "Ends with matching suffix",
14590            initial_state: "SubˇError".into(),
14591            buffer_marked_text: "<Sub|Error>".into(),
14592            completion_label: "SubscriptionError",
14593            completion_text: "SubscriptionError",
14594            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14595            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14596            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14597            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14598        },
14599        Run {
14600            run_description: "Suffix is a subsequence -- contiguous",
14601            initial_state: "SubˇErr".into(),
14602            buffer_marked_text: "<Sub|Err>".into(),
14603            completion_label: "SubscriptionError",
14604            completion_text: "SubscriptionError",
14605            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14606            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14607            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14608            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14609        },
14610        Run {
14611            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14612            initial_state: "Suˇscrirr".into(),
14613            buffer_marked_text: "<Su|scrirr>".into(),
14614            completion_label: "SubscriptionError",
14615            completion_text: "SubscriptionError",
14616            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14617            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14618            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14619            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14620        },
14621        Run {
14622            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14623            initial_state: "foo(indˇix)".into(),
14624            buffer_marked_text: "foo(<ind|ix>)".into(),
14625            completion_label: "node_index",
14626            completion_text: "node_index",
14627            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14628            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14629            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14630            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14631        },
14632        Run {
14633            run_description: "Replace range ends before cursor - should extend to cursor",
14634            initial_state: "before editˇo after".into(),
14635            buffer_marked_text: "before <{ed}>it|o after".into(),
14636            completion_label: "editor",
14637            completion_text: "editor",
14638            expected_with_insert_mode: "before editorˇo after".into(),
14639            expected_with_replace_mode: "before editorˇo after".into(),
14640            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14641            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14642        },
14643        Run {
14644            run_description: "Uses label for suffix matching",
14645            initial_state: "before ediˇtor after".into(),
14646            buffer_marked_text: "before <edi|tor> after".into(),
14647            completion_label: "editor",
14648            completion_text: "editor()",
14649            expected_with_insert_mode: "before editor()ˇtor after".into(),
14650            expected_with_replace_mode: "before editor()ˇ after".into(),
14651            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14652            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14653        },
14654        Run {
14655            run_description: "Case insensitive subsequence and suffix matching",
14656            initial_state: "before EDiˇtoR after".into(),
14657            buffer_marked_text: "before <EDi|toR> after".into(),
14658            completion_label: "editor",
14659            completion_text: "editor",
14660            expected_with_insert_mode: "before editorˇtoR after".into(),
14661            expected_with_replace_mode: "before editorˇ after".into(),
14662            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14663            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14664        },
14665    ];
14666
14667    for run in runs {
14668        let run_variations = [
14669            (LspInsertMode::Insert, run.expected_with_insert_mode),
14670            (LspInsertMode::Replace, run.expected_with_replace_mode),
14671            (
14672                LspInsertMode::ReplaceSubsequence,
14673                run.expected_with_replace_subsequence_mode,
14674            ),
14675            (
14676                LspInsertMode::ReplaceSuffix,
14677                run.expected_with_replace_suffix_mode,
14678            ),
14679        ];
14680
14681        for (lsp_insert_mode, expected_text) in run_variations {
14682            eprintln!(
14683                "run = {:?}, mode = {lsp_insert_mode:.?}",
14684                run.run_description,
14685            );
14686
14687            update_test_language_settings(&mut cx, |settings| {
14688                settings.defaults.completions = Some(CompletionSettingsContent {
14689                    lsp_insert_mode: Some(lsp_insert_mode),
14690                    words: Some(WordsCompletionMode::Disabled),
14691                    words_min_length: Some(0),
14692                    ..Default::default()
14693                });
14694            });
14695
14696            cx.set_state(&run.initial_state);
14697
14698            // Set up resolve handler before showing completions, since resolve may be
14699            // triggered when menu becomes visible (for documentation), not just on confirm.
14700            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
14701                move |_, _, _| async move {
14702                    Ok(lsp::CompletionItem {
14703                        additional_text_edits: None,
14704                        ..Default::default()
14705                    })
14706                },
14707            );
14708
14709            cx.update_editor(|editor, window, cx| {
14710                editor.show_completions(&ShowCompletions, window, cx);
14711            });
14712
14713            let counter = Arc::new(AtomicUsize::new(0));
14714            handle_completion_request_with_insert_and_replace(
14715                &mut cx,
14716                &run.buffer_marked_text,
14717                vec![(run.completion_label, run.completion_text)],
14718                counter.clone(),
14719            )
14720            .await;
14721            cx.condition(|editor, _| editor.context_menu_visible())
14722                .await;
14723            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14724
14725            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14726                editor
14727                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14728                    .unwrap()
14729            });
14730            cx.assert_editor_state(&expected_text);
14731            apply_additional_edits.await.unwrap();
14732        }
14733    }
14734}
14735
14736#[gpui::test]
14737async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14738    init_test(cx, |_| {});
14739    let mut cx = EditorLspTestContext::new_rust(
14740        lsp::ServerCapabilities {
14741            completion_provider: Some(lsp::CompletionOptions {
14742                resolve_provider: Some(true),
14743                ..Default::default()
14744            }),
14745            ..Default::default()
14746        },
14747        cx,
14748    )
14749    .await;
14750
14751    let initial_state = "SubˇError";
14752    let buffer_marked_text = "<Sub|Error>";
14753    let completion_text = "SubscriptionError";
14754    let expected_with_insert_mode = "SubscriptionErrorˇError";
14755    let expected_with_replace_mode = "SubscriptionErrorˇ";
14756
14757    update_test_language_settings(&mut cx, |settings| {
14758        settings.defaults.completions = Some(CompletionSettingsContent {
14759            words: Some(WordsCompletionMode::Disabled),
14760            words_min_length: Some(0),
14761            // set the opposite here to ensure that the action is overriding the default behavior
14762            lsp_insert_mode: Some(LspInsertMode::Insert),
14763            ..Default::default()
14764        });
14765    });
14766
14767    cx.set_state(initial_state);
14768    cx.update_editor(|editor, window, cx| {
14769        editor.show_completions(&ShowCompletions, window, cx);
14770    });
14771
14772    let counter = Arc::new(AtomicUsize::new(0));
14773    handle_completion_request_with_insert_and_replace(
14774        &mut cx,
14775        buffer_marked_text,
14776        vec![(completion_text, completion_text)],
14777        counter.clone(),
14778    )
14779    .await;
14780    cx.condition(|editor, _| editor.context_menu_visible())
14781        .await;
14782    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14783
14784    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14785        editor
14786            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14787            .unwrap()
14788    });
14789    cx.assert_editor_state(expected_with_replace_mode);
14790    handle_resolve_completion_request(&mut cx, None).await;
14791    apply_additional_edits.await.unwrap();
14792
14793    update_test_language_settings(&mut cx, |settings| {
14794        settings.defaults.completions = Some(CompletionSettingsContent {
14795            words: Some(WordsCompletionMode::Disabled),
14796            words_min_length: Some(0),
14797            // set the opposite here to ensure that the action is overriding the default behavior
14798            lsp_insert_mode: Some(LspInsertMode::Replace),
14799            ..Default::default()
14800        });
14801    });
14802
14803    cx.set_state(initial_state);
14804    cx.update_editor(|editor, window, cx| {
14805        editor.show_completions(&ShowCompletions, window, cx);
14806    });
14807    handle_completion_request_with_insert_and_replace(
14808        &mut cx,
14809        buffer_marked_text,
14810        vec![(completion_text, completion_text)],
14811        counter.clone(),
14812    )
14813    .await;
14814    cx.condition(|editor, _| editor.context_menu_visible())
14815        .await;
14816    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14817
14818    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14819        editor
14820            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14821            .unwrap()
14822    });
14823    cx.assert_editor_state(expected_with_insert_mode);
14824    handle_resolve_completion_request(&mut cx, None).await;
14825    apply_additional_edits.await.unwrap();
14826}
14827
14828#[gpui::test]
14829async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14830    init_test(cx, |_| {});
14831    let mut cx = EditorLspTestContext::new_rust(
14832        lsp::ServerCapabilities {
14833            completion_provider: Some(lsp::CompletionOptions {
14834                resolve_provider: Some(true),
14835                ..Default::default()
14836            }),
14837            ..Default::default()
14838        },
14839        cx,
14840    )
14841    .await;
14842
14843    // scenario: surrounding text matches completion text
14844    let completion_text = "to_offset";
14845    let initial_state = indoc! {"
14846        1. buf.to_offˇsuffix
14847        2. buf.to_offˇsuf
14848        3. buf.to_offˇfix
14849        4. buf.to_offˇ
14850        5. into_offˇensive
14851        6. ˇsuffix
14852        7. let ˇ //
14853        8. aaˇzz
14854        9. buf.to_off«zzzzzˇ»suffix
14855        10. buf.«ˇzzzzz»suffix
14856        11. to_off«ˇzzzzz»
14857
14858        buf.to_offˇsuffix  // newest cursor
14859    "};
14860    let completion_marked_buffer = indoc! {"
14861        1. buf.to_offsuffix
14862        2. buf.to_offsuf
14863        3. buf.to_offfix
14864        4. buf.to_off
14865        5. into_offensive
14866        6. suffix
14867        7. let  //
14868        8. aazz
14869        9. buf.to_offzzzzzsuffix
14870        10. buf.zzzzzsuffix
14871        11. to_offzzzzz
14872
14873        buf.<to_off|suffix>  // newest cursor
14874    "};
14875    let expected = indoc! {"
14876        1. buf.to_offsetˇ
14877        2. buf.to_offsetˇsuf
14878        3. buf.to_offsetˇfix
14879        4. buf.to_offsetˇ
14880        5. into_offsetˇensive
14881        6. to_offsetˇsuffix
14882        7. let to_offsetˇ //
14883        8. aato_offsetˇzz
14884        9. buf.to_offsetˇ
14885        10. buf.to_offsetˇsuffix
14886        11. to_offsetˇ
14887
14888        buf.to_offsetˇ  // newest cursor
14889    "};
14890    cx.set_state(initial_state);
14891    cx.update_editor(|editor, window, cx| {
14892        editor.show_completions(&ShowCompletions, window, cx);
14893    });
14894    handle_completion_request_with_insert_and_replace(
14895        &mut cx,
14896        completion_marked_buffer,
14897        vec![(completion_text, completion_text)],
14898        Arc::new(AtomicUsize::new(0)),
14899    )
14900    .await;
14901    cx.condition(|editor, _| editor.context_menu_visible())
14902        .await;
14903    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14904        editor
14905            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14906            .unwrap()
14907    });
14908    cx.assert_editor_state(expected);
14909    handle_resolve_completion_request(&mut cx, None).await;
14910    apply_additional_edits.await.unwrap();
14911
14912    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14913    let completion_text = "foo_and_bar";
14914    let initial_state = indoc! {"
14915        1. ooanbˇ
14916        2. zooanbˇ
14917        3. ooanbˇz
14918        4. zooanbˇz
14919        5. ooanˇ
14920        6. oanbˇ
14921
14922        ooanbˇ
14923    "};
14924    let completion_marked_buffer = indoc! {"
14925        1. ooanb
14926        2. zooanb
14927        3. ooanbz
14928        4. zooanbz
14929        5. ooan
14930        6. oanb
14931
14932        <ooanb|>
14933    "};
14934    let expected = indoc! {"
14935        1. foo_and_barˇ
14936        2. zfoo_and_barˇ
14937        3. foo_and_barˇz
14938        4. zfoo_and_barˇz
14939        5. ooanfoo_and_barˇ
14940        6. oanbfoo_and_barˇ
14941
14942        foo_and_barˇ
14943    "};
14944    cx.set_state(initial_state);
14945    cx.update_editor(|editor, window, cx| {
14946        editor.show_completions(&ShowCompletions, window, cx);
14947    });
14948    handle_completion_request_with_insert_and_replace(
14949        &mut cx,
14950        completion_marked_buffer,
14951        vec![(completion_text, completion_text)],
14952        Arc::new(AtomicUsize::new(0)),
14953    )
14954    .await;
14955    cx.condition(|editor, _| editor.context_menu_visible())
14956        .await;
14957    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14958        editor
14959            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14960            .unwrap()
14961    });
14962    cx.assert_editor_state(expected);
14963    handle_resolve_completion_request(&mut cx, None).await;
14964    apply_additional_edits.await.unwrap();
14965
14966    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14967    // (expects the same as if it was inserted at the end)
14968    let completion_text = "foo_and_bar";
14969    let initial_state = indoc! {"
14970        1. ooˇanb
14971        2. zooˇanb
14972        3. ooˇanbz
14973        4. zooˇanbz
14974
14975        ooˇanb
14976    "};
14977    let completion_marked_buffer = indoc! {"
14978        1. ooanb
14979        2. zooanb
14980        3. ooanbz
14981        4. zooanbz
14982
14983        <oo|anb>
14984    "};
14985    let expected = indoc! {"
14986        1. foo_and_barˇ
14987        2. zfoo_and_barˇ
14988        3. foo_and_barˇz
14989        4. zfoo_and_barˇz
14990
14991        foo_and_barˇ
14992    "};
14993    cx.set_state(initial_state);
14994    cx.update_editor(|editor, window, cx| {
14995        editor.show_completions(&ShowCompletions, window, cx);
14996    });
14997    handle_completion_request_with_insert_and_replace(
14998        &mut cx,
14999        completion_marked_buffer,
15000        vec![(completion_text, completion_text)],
15001        Arc::new(AtomicUsize::new(0)),
15002    )
15003    .await;
15004    cx.condition(|editor, _| editor.context_menu_visible())
15005        .await;
15006    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15007        editor
15008            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15009            .unwrap()
15010    });
15011    cx.assert_editor_state(expected);
15012    handle_resolve_completion_request(&mut cx, None).await;
15013    apply_additional_edits.await.unwrap();
15014}
15015
15016// This used to crash
15017#[gpui::test]
15018async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
15019    init_test(cx, |_| {});
15020
15021    let buffer_text = indoc! {"
15022        fn main() {
15023            10.satu;
15024
15025            //
15026            // separate cursors so they open in different excerpts (manually reproducible)
15027            //
15028
15029            10.satu20;
15030        }
15031    "};
15032    let multibuffer_text_with_selections = indoc! {"
15033        fn main() {
15034            10.satuˇ;
15035
15036            //
15037
15038            //
15039
15040            10.satuˇ20;
15041        }
15042    "};
15043    let expected_multibuffer = indoc! {"
15044        fn main() {
15045            10.saturating_sub()ˇ;
15046
15047            //
15048
15049            //
15050
15051            10.saturating_sub()ˇ;
15052        }
15053    "};
15054
15055    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
15056    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
15057
15058    let fs = FakeFs::new(cx.executor());
15059    fs.insert_tree(
15060        path!("/a"),
15061        json!({
15062            "main.rs": buffer_text,
15063        }),
15064    )
15065    .await;
15066
15067    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15068    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15069    language_registry.add(rust_lang());
15070    let mut fake_servers = language_registry.register_fake_lsp(
15071        "Rust",
15072        FakeLspAdapter {
15073            capabilities: lsp::ServerCapabilities {
15074                completion_provider: Some(lsp::CompletionOptions {
15075                    resolve_provider: None,
15076                    ..lsp::CompletionOptions::default()
15077                }),
15078                ..lsp::ServerCapabilities::default()
15079            },
15080            ..FakeLspAdapter::default()
15081        },
15082    );
15083    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15084    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15085    let buffer = project
15086        .update(cx, |project, cx| {
15087            project.open_local_buffer(path!("/a/main.rs"), cx)
15088        })
15089        .await
15090        .unwrap();
15091
15092    let multi_buffer = cx.new(|cx| {
15093        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
15094        multi_buffer.push_excerpts(
15095            buffer.clone(),
15096            [ExcerptRange::new(0..first_excerpt_end)],
15097            cx,
15098        );
15099        multi_buffer.push_excerpts(
15100            buffer.clone(),
15101            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
15102            cx,
15103        );
15104        multi_buffer
15105    });
15106
15107    let editor = workspace
15108        .update(cx, |_, window, cx| {
15109            cx.new(|cx| {
15110                Editor::new(
15111                    EditorMode::Full {
15112                        scale_ui_elements_with_buffer_font_size: false,
15113                        show_active_line_background: false,
15114                        sizing_behavior: SizingBehavior::Default,
15115                    },
15116                    multi_buffer.clone(),
15117                    Some(project.clone()),
15118                    window,
15119                    cx,
15120                )
15121            })
15122        })
15123        .unwrap();
15124
15125    let pane = workspace
15126        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15127        .unwrap();
15128    pane.update_in(cx, |pane, window, cx| {
15129        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
15130    });
15131
15132    let fake_server = fake_servers.next().await.unwrap();
15133    cx.run_until_parked();
15134
15135    editor.update_in(cx, |editor, window, cx| {
15136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15137            s.select_ranges([
15138                Point::new(1, 11)..Point::new(1, 11),
15139                Point::new(7, 11)..Point::new(7, 11),
15140            ])
15141        });
15142
15143        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
15144    });
15145
15146    editor.update_in(cx, |editor, window, cx| {
15147        editor.show_completions(&ShowCompletions, window, cx);
15148    });
15149
15150    fake_server
15151        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15152            let completion_item = lsp::CompletionItem {
15153                label: "saturating_sub()".into(),
15154                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15155                    lsp::InsertReplaceEdit {
15156                        new_text: "saturating_sub()".to_owned(),
15157                        insert: lsp::Range::new(
15158                            lsp::Position::new(7, 7),
15159                            lsp::Position::new(7, 11),
15160                        ),
15161                        replace: lsp::Range::new(
15162                            lsp::Position::new(7, 7),
15163                            lsp::Position::new(7, 13),
15164                        ),
15165                    },
15166                )),
15167                ..lsp::CompletionItem::default()
15168            };
15169
15170            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
15171        })
15172        .next()
15173        .await
15174        .unwrap();
15175
15176    cx.condition(&editor, |editor, _| editor.context_menu_visible())
15177        .await;
15178
15179    editor
15180        .update_in(cx, |editor, window, cx| {
15181            editor
15182                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15183                .unwrap()
15184        })
15185        .await
15186        .unwrap();
15187
15188    editor.update(cx, |editor, cx| {
15189        assert_text_with_selections(editor, expected_multibuffer, cx);
15190    })
15191}
15192
15193#[gpui::test]
15194async fn test_completion(cx: &mut TestAppContext) {
15195    init_test(cx, |_| {});
15196
15197    let mut cx = EditorLspTestContext::new_rust(
15198        lsp::ServerCapabilities {
15199            completion_provider: Some(lsp::CompletionOptions {
15200                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15201                resolve_provider: Some(true),
15202                ..Default::default()
15203            }),
15204            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15205            ..Default::default()
15206        },
15207        cx,
15208    )
15209    .await;
15210    let counter = Arc::new(AtomicUsize::new(0));
15211
15212    cx.set_state(indoc! {"
15213        oneˇ
15214        two
15215        three
15216    "});
15217    cx.simulate_keystroke(".");
15218    handle_completion_request(
15219        indoc! {"
15220            one.|<>
15221            two
15222            three
15223        "},
15224        vec!["first_completion", "second_completion"],
15225        true,
15226        counter.clone(),
15227        &mut cx,
15228    )
15229    .await;
15230    cx.condition(|editor, _| editor.context_menu_visible())
15231        .await;
15232    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15233
15234    let _handler = handle_signature_help_request(
15235        &mut cx,
15236        lsp::SignatureHelp {
15237            signatures: vec![lsp::SignatureInformation {
15238                label: "test signature".to_string(),
15239                documentation: None,
15240                parameters: Some(vec![lsp::ParameterInformation {
15241                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
15242                    documentation: None,
15243                }]),
15244                active_parameter: None,
15245            }],
15246            active_signature: None,
15247            active_parameter: None,
15248        },
15249    );
15250    cx.update_editor(|editor, window, cx| {
15251        assert!(
15252            !editor.signature_help_state.is_shown(),
15253            "No signature help was called for"
15254        );
15255        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15256    });
15257    cx.run_until_parked();
15258    cx.update_editor(|editor, _, _| {
15259        assert!(
15260            !editor.signature_help_state.is_shown(),
15261            "No signature help should be shown when completions menu is open"
15262        );
15263    });
15264
15265    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15266        editor.context_menu_next(&Default::default(), window, cx);
15267        editor
15268            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15269            .unwrap()
15270    });
15271    cx.assert_editor_state(indoc! {"
15272        one.second_completionˇ
15273        two
15274        three
15275    "});
15276
15277    handle_resolve_completion_request(
15278        &mut cx,
15279        Some(vec![
15280            (
15281                //This overlaps with the primary completion edit which is
15282                //misbehavior from the LSP spec, test that we filter it out
15283                indoc! {"
15284                    one.second_ˇcompletion
15285                    two
15286                    threeˇ
15287                "},
15288                "overlapping additional edit",
15289            ),
15290            (
15291                indoc! {"
15292                    one.second_completion
15293                    two
15294                    threeˇ
15295                "},
15296                "\nadditional edit",
15297            ),
15298        ]),
15299    )
15300    .await;
15301    apply_additional_edits.await.unwrap();
15302    cx.assert_editor_state(indoc! {"
15303        one.second_completionˇ
15304        two
15305        three
15306        additional edit
15307    "});
15308
15309    cx.set_state(indoc! {"
15310        one.second_completion
15311        twoˇ
15312        threeˇ
15313        additional edit
15314    "});
15315    cx.simulate_keystroke(" ");
15316    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15317    cx.simulate_keystroke("s");
15318    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15319
15320    cx.assert_editor_state(indoc! {"
15321        one.second_completion
15322        two sˇ
15323        three sˇ
15324        additional edit
15325    "});
15326    handle_completion_request(
15327        indoc! {"
15328            one.second_completion
15329            two s
15330            three <s|>
15331            additional edit
15332        "},
15333        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15334        true,
15335        counter.clone(),
15336        &mut cx,
15337    )
15338    .await;
15339    cx.condition(|editor, _| editor.context_menu_visible())
15340        .await;
15341    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15342
15343    cx.simulate_keystroke("i");
15344
15345    handle_completion_request(
15346        indoc! {"
15347            one.second_completion
15348            two si
15349            three <si|>
15350            additional edit
15351        "},
15352        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15353        true,
15354        counter.clone(),
15355        &mut cx,
15356    )
15357    .await;
15358    cx.condition(|editor, _| editor.context_menu_visible())
15359        .await;
15360    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15361
15362    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15363        editor
15364            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15365            .unwrap()
15366    });
15367    cx.assert_editor_state(indoc! {"
15368        one.second_completion
15369        two sixth_completionˇ
15370        three sixth_completionˇ
15371        additional edit
15372    "});
15373
15374    apply_additional_edits.await.unwrap();
15375
15376    update_test_language_settings(&mut cx, |settings| {
15377        settings.defaults.show_completions_on_input = Some(false);
15378    });
15379    cx.set_state("editorˇ");
15380    cx.simulate_keystroke(".");
15381    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15382    cx.simulate_keystrokes("c l o");
15383    cx.assert_editor_state("editor.cloˇ");
15384    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15385    cx.update_editor(|editor, window, cx| {
15386        editor.show_completions(&ShowCompletions, window, cx);
15387    });
15388    handle_completion_request(
15389        "editor.<clo|>",
15390        vec!["close", "clobber"],
15391        true,
15392        counter.clone(),
15393        &mut cx,
15394    )
15395    .await;
15396    cx.condition(|editor, _| editor.context_menu_visible())
15397        .await;
15398    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15399
15400    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15401        editor
15402            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15403            .unwrap()
15404    });
15405    cx.assert_editor_state("editor.clobberˇ");
15406    handle_resolve_completion_request(&mut cx, None).await;
15407    apply_additional_edits.await.unwrap();
15408}
15409
15410#[gpui::test]
15411async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15412    init_test(cx, |_| {});
15413
15414    let fs = FakeFs::new(cx.executor());
15415    fs.insert_tree(
15416        path!("/a"),
15417        json!({
15418            "main.rs": "",
15419        }),
15420    )
15421    .await;
15422
15423    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15424    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15425    language_registry.add(rust_lang());
15426    let command_calls = Arc::new(AtomicUsize::new(0));
15427    let registered_command = "_the/command";
15428
15429    let closure_command_calls = command_calls.clone();
15430    let mut fake_servers = language_registry.register_fake_lsp(
15431        "Rust",
15432        FakeLspAdapter {
15433            capabilities: lsp::ServerCapabilities {
15434                completion_provider: Some(lsp::CompletionOptions {
15435                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15436                    ..lsp::CompletionOptions::default()
15437                }),
15438                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15439                    commands: vec![registered_command.to_owned()],
15440                    ..lsp::ExecuteCommandOptions::default()
15441                }),
15442                ..lsp::ServerCapabilities::default()
15443            },
15444            initializer: Some(Box::new(move |fake_server| {
15445                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15446                    move |params, _| async move {
15447                        Ok(Some(lsp::CompletionResponse::Array(vec![
15448                            lsp::CompletionItem {
15449                                label: "registered_command".to_owned(),
15450                                text_edit: gen_text_edit(&params, ""),
15451                                command: Some(lsp::Command {
15452                                    title: registered_command.to_owned(),
15453                                    command: "_the/command".to_owned(),
15454                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15455                                }),
15456                                ..lsp::CompletionItem::default()
15457                            },
15458                            lsp::CompletionItem {
15459                                label: "unregistered_command".to_owned(),
15460                                text_edit: gen_text_edit(&params, ""),
15461                                command: Some(lsp::Command {
15462                                    title: "????????????".to_owned(),
15463                                    command: "????????????".to_owned(),
15464                                    arguments: Some(vec![serde_json::Value::Null]),
15465                                }),
15466                                ..lsp::CompletionItem::default()
15467                            },
15468                        ])))
15469                    },
15470                );
15471                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15472                    let command_calls = closure_command_calls.clone();
15473                    move |params, _| {
15474                        assert_eq!(params.command, registered_command);
15475                        let command_calls = command_calls.clone();
15476                        async move {
15477                            command_calls.fetch_add(1, atomic::Ordering::Release);
15478                            Ok(Some(json!(null)))
15479                        }
15480                    }
15481                });
15482            })),
15483            ..FakeLspAdapter::default()
15484        },
15485    );
15486    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15487    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15488    let editor = workspace
15489        .update(cx, |workspace, window, cx| {
15490            workspace.open_abs_path(
15491                PathBuf::from(path!("/a/main.rs")),
15492                OpenOptions::default(),
15493                window,
15494                cx,
15495            )
15496        })
15497        .unwrap()
15498        .await
15499        .unwrap()
15500        .downcast::<Editor>()
15501        .unwrap();
15502    let _fake_server = fake_servers.next().await.unwrap();
15503    cx.run_until_parked();
15504
15505    editor.update_in(cx, |editor, window, cx| {
15506        cx.focus_self(window);
15507        editor.move_to_end(&MoveToEnd, window, cx);
15508        editor.handle_input(".", window, cx);
15509    });
15510    cx.run_until_parked();
15511    editor.update(cx, |editor, _| {
15512        assert!(editor.context_menu_visible());
15513        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15514        {
15515            let completion_labels = menu
15516                .completions
15517                .borrow()
15518                .iter()
15519                .map(|c| c.label.text.clone())
15520                .collect::<Vec<_>>();
15521            assert_eq!(
15522                completion_labels,
15523                &["registered_command", "unregistered_command",],
15524            );
15525        } else {
15526            panic!("expected completion menu to be open");
15527        }
15528    });
15529
15530    editor
15531        .update_in(cx, |editor, window, cx| {
15532            editor
15533                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15534                .unwrap()
15535        })
15536        .await
15537        .unwrap();
15538    cx.run_until_parked();
15539    assert_eq!(
15540        command_calls.load(atomic::Ordering::Acquire),
15541        1,
15542        "For completion with a registered command, Zed should send a command execution request",
15543    );
15544
15545    editor.update_in(cx, |editor, window, cx| {
15546        cx.focus_self(window);
15547        editor.handle_input(".", window, cx);
15548    });
15549    cx.run_until_parked();
15550    editor.update(cx, |editor, _| {
15551        assert!(editor.context_menu_visible());
15552        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15553        {
15554            let completion_labels = menu
15555                .completions
15556                .borrow()
15557                .iter()
15558                .map(|c| c.label.text.clone())
15559                .collect::<Vec<_>>();
15560            assert_eq!(
15561                completion_labels,
15562                &["registered_command", "unregistered_command",],
15563            );
15564        } else {
15565            panic!("expected completion menu to be open");
15566        }
15567    });
15568    editor
15569        .update_in(cx, |editor, window, cx| {
15570            editor.context_menu_next(&Default::default(), window, cx);
15571            editor
15572                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15573                .unwrap()
15574        })
15575        .await
15576        .unwrap();
15577    cx.run_until_parked();
15578    assert_eq!(
15579        command_calls.load(atomic::Ordering::Acquire),
15580        1,
15581        "For completion with an unregistered command, Zed should not send a command execution request",
15582    );
15583}
15584
15585#[gpui::test]
15586async fn test_completion_reuse(cx: &mut TestAppContext) {
15587    init_test(cx, |_| {});
15588
15589    let mut cx = EditorLspTestContext::new_rust(
15590        lsp::ServerCapabilities {
15591            completion_provider: Some(lsp::CompletionOptions {
15592                trigger_characters: Some(vec![".".to_string()]),
15593                ..Default::default()
15594            }),
15595            ..Default::default()
15596        },
15597        cx,
15598    )
15599    .await;
15600
15601    let counter = Arc::new(AtomicUsize::new(0));
15602    cx.set_state("objˇ");
15603    cx.simulate_keystroke(".");
15604
15605    // Initial completion request returns complete results
15606    let is_incomplete = false;
15607    handle_completion_request(
15608        "obj.|<>",
15609        vec!["a", "ab", "abc"],
15610        is_incomplete,
15611        counter.clone(),
15612        &mut cx,
15613    )
15614    .await;
15615    cx.run_until_parked();
15616    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15617    cx.assert_editor_state("obj.ˇ");
15618    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15619
15620    // Type "a" - filters existing completions
15621    cx.simulate_keystroke("a");
15622    cx.run_until_parked();
15623    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15624    cx.assert_editor_state("obj.aˇ");
15625    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15626
15627    // Type "b" - filters existing completions
15628    cx.simulate_keystroke("b");
15629    cx.run_until_parked();
15630    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15631    cx.assert_editor_state("obj.abˇ");
15632    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15633
15634    // Type "c" - filters existing completions
15635    cx.simulate_keystroke("c");
15636    cx.run_until_parked();
15637    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15638    cx.assert_editor_state("obj.abcˇ");
15639    check_displayed_completions(vec!["abc"], &mut cx);
15640
15641    // Backspace to delete "c" - filters existing completions
15642    cx.update_editor(|editor, window, cx| {
15643        editor.backspace(&Backspace, window, cx);
15644    });
15645    cx.run_until_parked();
15646    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15647    cx.assert_editor_state("obj.abˇ");
15648    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15649
15650    // Moving cursor to the left dismisses menu.
15651    cx.update_editor(|editor, window, cx| {
15652        editor.move_left(&MoveLeft, window, cx);
15653    });
15654    cx.run_until_parked();
15655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15656    cx.assert_editor_state("obj.aˇb");
15657    cx.update_editor(|editor, _, _| {
15658        assert_eq!(editor.context_menu_visible(), false);
15659    });
15660
15661    // Type "b" - new request
15662    cx.simulate_keystroke("b");
15663    let is_incomplete = false;
15664    handle_completion_request(
15665        "obj.<ab|>a",
15666        vec!["ab", "abc"],
15667        is_incomplete,
15668        counter.clone(),
15669        &mut cx,
15670    )
15671    .await;
15672    cx.run_until_parked();
15673    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15674    cx.assert_editor_state("obj.abˇb");
15675    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15676
15677    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15678    cx.update_editor(|editor, window, cx| {
15679        editor.backspace(&Backspace, window, cx);
15680    });
15681    let is_incomplete = false;
15682    handle_completion_request(
15683        "obj.<a|>b",
15684        vec!["a", "ab", "abc"],
15685        is_incomplete,
15686        counter.clone(),
15687        &mut cx,
15688    )
15689    .await;
15690    cx.run_until_parked();
15691    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15692    cx.assert_editor_state("obj.aˇb");
15693    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15694
15695    // Backspace to delete "a" - dismisses menu.
15696    cx.update_editor(|editor, window, cx| {
15697        editor.backspace(&Backspace, window, cx);
15698    });
15699    cx.run_until_parked();
15700    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15701    cx.assert_editor_state("obj.ˇb");
15702    cx.update_editor(|editor, _, _| {
15703        assert_eq!(editor.context_menu_visible(), false);
15704    });
15705}
15706
15707#[gpui::test]
15708async fn test_word_completion(cx: &mut TestAppContext) {
15709    let lsp_fetch_timeout_ms = 10;
15710    init_test(cx, |language_settings| {
15711        language_settings.defaults.completions = Some(CompletionSettingsContent {
15712            words_min_length: Some(0),
15713            lsp_fetch_timeout_ms: Some(10),
15714            lsp_insert_mode: Some(LspInsertMode::Insert),
15715            ..Default::default()
15716        });
15717    });
15718
15719    let mut cx = EditorLspTestContext::new_rust(
15720        lsp::ServerCapabilities {
15721            completion_provider: Some(lsp::CompletionOptions {
15722                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15723                ..lsp::CompletionOptions::default()
15724            }),
15725            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15726            ..lsp::ServerCapabilities::default()
15727        },
15728        cx,
15729    )
15730    .await;
15731
15732    let throttle_completions = Arc::new(AtomicBool::new(false));
15733
15734    let lsp_throttle_completions = throttle_completions.clone();
15735    let _completion_requests_handler =
15736        cx.lsp
15737            .server
15738            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15739                let lsp_throttle_completions = lsp_throttle_completions.clone();
15740                let cx = cx.clone();
15741                async move {
15742                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15743                        cx.background_executor()
15744                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15745                            .await;
15746                    }
15747                    Ok(Some(lsp::CompletionResponse::Array(vec![
15748                        lsp::CompletionItem {
15749                            label: "first".into(),
15750                            ..lsp::CompletionItem::default()
15751                        },
15752                        lsp::CompletionItem {
15753                            label: "last".into(),
15754                            ..lsp::CompletionItem::default()
15755                        },
15756                    ])))
15757                }
15758            });
15759
15760    cx.set_state(indoc! {"
15761        oneˇ
15762        two
15763        three
15764    "});
15765    cx.simulate_keystroke(".");
15766    cx.executor().run_until_parked();
15767    cx.condition(|editor, _| editor.context_menu_visible())
15768        .await;
15769    cx.update_editor(|editor, window, cx| {
15770        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15771        {
15772            assert_eq!(
15773                completion_menu_entries(menu),
15774                &["first", "last"],
15775                "When LSP server is fast to reply, no fallback word completions are used"
15776            );
15777        } else {
15778            panic!("expected completion menu to be open");
15779        }
15780        editor.cancel(&Cancel, window, cx);
15781    });
15782    cx.executor().run_until_parked();
15783    cx.condition(|editor, _| !editor.context_menu_visible())
15784        .await;
15785
15786    throttle_completions.store(true, atomic::Ordering::Release);
15787    cx.simulate_keystroke(".");
15788    cx.executor()
15789        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15790    cx.executor().run_until_parked();
15791    cx.condition(|editor, _| editor.context_menu_visible())
15792        .await;
15793    cx.update_editor(|editor, _, _| {
15794        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15795        {
15796            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15797                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15798        } else {
15799            panic!("expected completion menu to be open");
15800        }
15801    });
15802}
15803
15804#[gpui::test]
15805async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15806    init_test(cx, |language_settings| {
15807        language_settings.defaults.completions = Some(CompletionSettingsContent {
15808            words: Some(WordsCompletionMode::Enabled),
15809            words_min_length: Some(0),
15810            lsp_insert_mode: Some(LspInsertMode::Insert),
15811            ..Default::default()
15812        });
15813    });
15814
15815    let mut cx = EditorLspTestContext::new_rust(
15816        lsp::ServerCapabilities {
15817            completion_provider: Some(lsp::CompletionOptions {
15818                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15819                ..lsp::CompletionOptions::default()
15820            }),
15821            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15822            ..lsp::ServerCapabilities::default()
15823        },
15824        cx,
15825    )
15826    .await;
15827
15828    let _completion_requests_handler =
15829        cx.lsp
15830            .server
15831            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15832                Ok(Some(lsp::CompletionResponse::Array(vec![
15833                    lsp::CompletionItem {
15834                        label: "first".into(),
15835                        ..lsp::CompletionItem::default()
15836                    },
15837                    lsp::CompletionItem {
15838                        label: "last".into(),
15839                        ..lsp::CompletionItem::default()
15840                    },
15841                ])))
15842            });
15843
15844    cx.set_state(indoc! {"ˇ
15845        first
15846        last
15847        second
15848    "});
15849    cx.simulate_keystroke(".");
15850    cx.executor().run_until_parked();
15851    cx.condition(|editor, _| editor.context_menu_visible())
15852        .await;
15853    cx.update_editor(|editor, _, _| {
15854        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15855        {
15856            assert_eq!(
15857                completion_menu_entries(menu),
15858                &["first", "last", "second"],
15859                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15860            );
15861        } else {
15862            panic!("expected completion menu to be open");
15863        }
15864    });
15865}
15866
15867#[gpui::test]
15868async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15869    init_test(cx, |language_settings| {
15870        language_settings.defaults.completions = Some(CompletionSettingsContent {
15871            words: Some(WordsCompletionMode::Disabled),
15872            words_min_length: Some(0),
15873            lsp_insert_mode: Some(LspInsertMode::Insert),
15874            ..Default::default()
15875        });
15876    });
15877
15878    let mut cx = EditorLspTestContext::new_rust(
15879        lsp::ServerCapabilities {
15880            completion_provider: Some(lsp::CompletionOptions {
15881                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15882                ..lsp::CompletionOptions::default()
15883            }),
15884            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15885            ..lsp::ServerCapabilities::default()
15886        },
15887        cx,
15888    )
15889    .await;
15890
15891    let _completion_requests_handler =
15892        cx.lsp
15893            .server
15894            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15895                panic!("LSP completions should not be queried when dealing with word completions")
15896            });
15897
15898    cx.set_state(indoc! {"ˇ
15899        first
15900        last
15901        second
15902    "});
15903    cx.update_editor(|editor, window, cx| {
15904        editor.show_word_completions(&ShowWordCompletions, window, cx);
15905    });
15906    cx.executor().run_until_parked();
15907    cx.condition(|editor, _| editor.context_menu_visible())
15908        .await;
15909    cx.update_editor(|editor, _, _| {
15910        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15911        {
15912            assert_eq!(
15913                completion_menu_entries(menu),
15914                &["first", "last", "second"],
15915                "`ShowWordCompletions` action should show word completions"
15916            );
15917        } else {
15918            panic!("expected completion menu to be open");
15919        }
15920    });
15921
15922    cx.simulate_keystroke("l");
15923    cx.executor().run_until_parked();
15924    cx.condition(|editor, _| editor.context_menu_visible())
15925        .await;
15926    cx.update_editor(|editor, _, _| {
15927        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15928        {
15929            assert_eq!(
15930                completion_menu_entries(menu),
15931                &["last"],
15932                "After showing word completions, further editing should filter them and not query the LSP"
15933            );
15934        } else {
15935            panic!("expected completion menu to be open");
15936        }
15937    });
15938}
15939
15940#[gpui::test]
15941async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15942    init_test(cx, |language_settings| {
15943        language_settings.defaults.completions = Some(CompletionSettingsContent {
15944            words_min_length: Some(0),
15945            lsp: Some(false),
15946            lsp_insert_mode: Some(LspInsertMode::Insert),
15947            ..Default::default()
15948        });
15949    });
15950
15951    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15952
15953    cx.set_state(indoc! {"ˇ
15954        0_usize
15955        let
15956        33
15957        4.5f32
15958    "});
15959    cx.update_editor(|editor, window, cx| {
15960        editor.show_completions(&ShowCompletions, window, cx);
15961    });
15962    cx.executor().run_until_parked();
15963    cx.condition(|editor, _| editor.context_menu_visible())
15964        .await;
15965    cx.update_editor(|editor, window, cx| {
15966        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15967        {
15968            assert_eq!(
15969                completion_menu_entries(menu),
15970                &["let"],
15971                "With no digits in the completion query, no digits should be in the word completions"
15972            );
15973        } else {
15974            panic!("expected completion menu to be open");
15975        }
15976        editor.cancel(&Cancel, window, cx);
15977    });
15978
15979    cx.set_state(indoc! {"15980        0_usize
15981        let
15982        3
15983        33.35f32
15984    "});
15985    cx.update_editor(|editor, window, cx| {
15986        editor.show_completions(&ShowCompletions, window, cx);
15987    });
15988    cx.executor().run_until_parked();
15989    cx.condition(|editor, _| editor.context_menu_visible())
15990        .await;
15991    cx.update_editor(|editor, _, _| {
15992        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15993        {
15994            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15995                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15996        } else {
15997            panic!("expected completion menu to be open");
15998        }
15999    });
16000}
16001
16002#[gpui::test]
16003async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
16004    init_test(cx, |language_settings| {
16005        language_settings.defaults.completions = Some(CompletionSettingsContent {
16006            words: Some(WordsCompletionMode::Enabled),
16007            words_min_length: Some(3),
16008            lsp_insert_mode: Some(LspInsertMode::Insert),
16009            ..Default::default()
16010        });
16011    });
16012
16013    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16014    cx.set_state(indoc! {"ˇ
16015        wow
16016        wowen
16017        wowser
16018    "});
16019    cx.simulate_keystroke("w");
16020    cx.executor().run_until_parked();
16021    cx.update_editor(|editor, _, _| {
16022        if editor.context_menu.borrow_mut().is_some() {
16023            panic!(
16024                "expected completion menu to be hidden, as words completion threshold is not met"
16025            );
16026        }
16027    });
16028
16029    cx.update_editor(|editor, window, cx| {
16030        editor.show_word_completions(&ShowWordCompletions, window, cx);
16031    });
16032    cx.executor().run_until_parked();
16033    cx.update_editor(|editor, window, cx| {
16034        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16035        {
16036            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
16037        } else {
16038            panic!("expected completion menu to be open after the word completions are called with an action");
16039        }
16040
16041        editor.cancel(&Cancel, window, cx);
16042    });
16043    cx.update_editor(|editor, _, _| {
16044        if editor.context_menu.borrow_mut().is_some() {
16045            panic!("expected completion menu to be hidden after canceling");
16046        }
16047    });
16048
16049    cx.simulate_keystroke("o");
16050    cx.executor().run_until_parked();
16051    cx.update_editor(|editor, _, _| {
16052        if editor.context_menu.borrow_mut().is_some() {
16053            panic!(
16054                "expected completion menu to be hidden, as words completion threshold is not met still"
16055            );
16056        }
16057    });
16058
16059    cx.simulate_keystroke("w");
16060    cx.executor().run_until_parked();
16061    cx.update_editor(|editor, _, _| {
16062        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16063        {
16064            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
16065        } else {
16066            panic!("expected completion menu to be open after the word completions threshold is met");
16067        }
16068    });
16069}
16070
16071#[gpui::test]
16072async fn test_word_completions_disabled(cx: &mut TestAppContext) {
16073    init_test(cx, |language_settings| {
16074        language_settings.defaults.completions = Some(CompletionSettingsContent {
16075            words: Some(WordsCompletionMode::Enabled),
16076            words_min_length: Some(0),
16077            lsp_insert_mode: Some(LspInsertMode::Insert),
16078            ..Default::default()
16079        });
16080    });
16081
16082    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16083    cx.update_editor(|editor, _, _| {
16084        editor.disable_word_completions();
16085    });
16086    cx.set_state(indoc! {"ˇ
16087        wow
16088        wowen
16089        wowser
16090    "});
16091    cx.simulate_keystroke("w");
16092    cx.executor().run_until_parked();
16093    cx.update_editor(|editor, _, _| {
16094        if editor.context_menu.borrow_mut().is_some() {
16095            panic!(
16096                "expected completion menu to be hidden, as words completion are disabled for this editor"
16097            );
16098        }
16099    });
16100
16101    cx.update_editor(|editor, window, cx| {
16102        editor.show_word_completions(&ShowWordCompletions, window, cx);
16103    });
16104    cx.executor().run_until_parked();
16105    cx.update_editor(|editor, _, _| {
16106        if editor.context_menu.borrow_mut().is_some() {
16107            panic!(
16108                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
16109            );
16110        }
16111    });
16112}
16113
16114#[gpui::test]
16115async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
16116    init_test(cx, |language_settings| {
16117        language_settings.defaults.completions = Some(CompletionSettingsContent {
16118            words: Some(WordsCompletionMode::Disabled),
16119            words_min_length: Some(0),
16120            lsp_insert_mode: Some(LspInsertMode::Insert),
16121            ..Default::default()
16122        });
16123    });
16124
16125    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16126    cx.update_editor(|editor, _, _| {
16127        editor.set_completion_provider(None);
16128    });
16129    cx.set_state(indoc! {"ˇ
16130        wow
16131        wowen
16132        wowser
16133    "});
16134    cx.simulate_keystroke("w");
16135    cx.executor().run_until_parked();
16136    cx.update_editor(|editor, _, _| {
16137        if editor.context_menu.borrow_mut().is_some() {
16138            panic!("expected completion menu to be hidden, as disabled in settings");
16139        }
16140    });
16141}
16142
16143fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
16144    let position = || lsp::Position {
16145        line: params.text_document_position.position.line,
16146        character: params.text_document_position.position.character,
16147    };
16148    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16149        range: lsp::Range {
16150            start: position(),
16151            end: position(),
16152        },
16153        new_text: text.to_string(),
16154    }))
16155}
16156
16157#[gpui::test]
16158async fn test_multiline_completion(cx: &mut TestAppContext) {
16159    init_test(cx, |_| {});
16160
16161    let fs = FakeFs::new(cx.executor());
16162    fs.insert_tree(
16163        path!("/a"),
16164        json!({
16165            "main.ts": "a",
16166        }),
16167    )
16168    .await;
16169
16170    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16171    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16172    let typescript_language = Arc::new(Language::new(
16173        LanguageConfig {
16174            name: "TypeScript".into(),
16175            matcher: LanguageMatcher {
16176                path_suffixes: vec!["ts".to_string()],
16177                ..LanguageMatcher::default()
16178            },
16179            line_comments: vec!["// ".into()],
16180            ..LanguageConfig::default()
16181        },
16182        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16183    ));
16184    language_registry.add(typescript_language.clone());
16185    let mut fake_servers = language_registry.register_fake_lsp(
16186        "TypeScript",
16187        FakeLspAdapter {
16188            capabilities: lsp::ServerCapabilities {
16189                completion_provider: Some(lsp::CompletionOptions {
16190                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16191                    ..lsp::CompletionOptions::default()
16192                }),
16193                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16194                ..lsp::ServerCapabilities::default()
16195            },
16196            // Emulate vtsls label generation
16197            label_for_completion: Some(Box::new(|item, _| {
16198                let text = if let Some(description) = item
16199                    .label_details
16200                    .as_ref()
16201                    .and_then(|label_details| label_details.description.as_ref())
16202                {
16203                    format!("{} {}", item.label, description)
16204                } else if let Some(detail) = &item.detail {
16205                    format!("{} {}", item.label, detail)
16206                } else {
16207                    item.label.clone()
16208                };
16209                Some(language::CodeLabel::plain(text, None))
16210            })),
16211            ..FakeLspAdapter::default()
16212        },
16213    );
16214    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16215    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16216    let worktree_id = workspace
16217        .update(cx, |workspace, _window, cx| {
16218            workspace.project().update(cx, |project, cx| {
16219                project.worktrees(cx).next().unwrap().read(cx).id()
16220            })
16221        })
16222        .unwrap();
16223    let _buffer = project
16224        .update(cx, |project, cx| {
16225            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
16226        })
16227        .await
16228        .unwrap();
16229    let editor = workspace
16230        .update(cx, |workspace, window, cx| {
16231            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
16232        })
16233        .unwrap()
16234        .await
16235        .unwrap()
16236        .downcast::<Editor>()
16237        .unwrap();
16238    let fake_server = fake_servers.next().await.unwrap();
16239    cx.run_until_parked();
16240
16241    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
16242    let multiline_label_2 = "a\nb\nc\n";
16243    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
16244    let multiline_description = "d\ne\nf\n";
16245    let multiline_detail_2 = "g\nh\ni\n";
16246
16247    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16248        move |params, _| async move {
16249            Ok(Some(lsp::CompletionResponse::Array(vec![
16250                lsp::CompletionItem {
16251                    label: multiline_label.to_string(),
16252                    text_edit: gen_text_edit(&params, "new_text_1"),
16253                    ..lsp::CompletionItem::default()
16254                },
16255                lsp::CompletionItem {
16256                    label: "single line label 1".to_string(),
16257                    detail: Some(multiline_detail.to_string()),
16258                    text_edit: gen_text_edit(&params, "new_text_2"),
16259                    ..lsp::CompletionItem::default()
16260                },
16261                lsp::CompletionItem {
16262                    label: "single line label 2".to_string(),
16263                    label_details: Some(lsp::CompletionItemLabelDetails {
16264                        description: Some(multiline_description.to_string()),
16265                        detail: None,
16266                    }),
16267                    text_edit: gen_text_edit(&params, "new_text_2"),
16268                    ..lsp::CompletionItem::default()
16269                },
16270                lsp::CompletionItem {
16271                    label: multiline_label_2.to_string(),
16272                    detail: Some(multiline_detail_2.to_string()),
16273                    text_edit: gen_text_edit(&params, "new_text_3"),
16274                    ..lsp::CompletionItem::default()
16275                },
16276                lsp::CompletionItem {
16277                    label: "Label with many     spaces and \t but without newlines".to_string(),
16278                    detail: Some(
16279                        "Details with many     spaces and \t but without newlines".to_string(),
16280                    ),
16281                    text_edit: gen_text_edit(&params, "new_text_4"),
16282                    ..lsp::CompletionItem::default()
16283                },
16284            ])))
16285        },
16286    );
16287
16288    editor.update_in(cx, |editor, window, cx| {
16289        cx.focus_self(window);
16290        editor.move_to_end(&MoveToEnd, window, cx);
16291        editor.handle_input(".", window, cx);
16292    });
16293    cx.run_until_parked();
16294    completion_handle.next().await.unwrap();
16295
16296    editor.update(cx, |editor, _| {
16297        assert!(editor.context_menu_visible());
16298        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16299        {
16300            let completion_labels = menu
16301                .completions
16302                .borrow()
16303                .iter()
16304                .map(|c| c.label.text.clone())
16305                .collect::<Vec<_>>();
16306            assert_eq!(
16307                completion_labels,
16308                &[
16309                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16310                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16311                    "single line label 2 d e f ",
16312                    "a b c g h i ",
16313                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16314                ],
16315                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16316            );
16317
16318            for completion in menu
16319                .completions
16320                .borrow()
16321                .iter() {
16322                    assert_eq!(
16323                        completion.label.filter_range,
16324                        0..completion.label.text.len(),
16325                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16326                    );
16327                }
16328        } else {
16329            panic!("expected completion menu to be open");
16330        }
16331    });
16332}
16333
16334#[gpui::test]
16335async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16336    init_test(cx, |_| {});
16337    let mut cx = EditorLspTestContext::new_rust(
16338        lsp::ServerCapabilities {
16339            completion_provider: Some(lsp::CompletionOptions {
16340                trigger_characters: Some(vec![".".to_string()]),
16341                ..Default::default()
16342            }),
16343            ..Default::default()
16344        },
16345        cx,
16346    )
16347    .await;
16348    cx.lsp
16349        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16350            Ok(Some(lsp::CompletionResponse::Array(vec![
16351                lsp::CompletionItem {
16352                    label: "first".into(),
16353                    ..Default::default()
16354                },
16355                lsp::CompletionItem {
16356                    label: "last".into(),
16357                    ..Default::default()
16358                },
16359            ])))
16360        });
16361    cx.set_state("variableˇ");
16362    cx.simulate_keystroke(".");
16363    cx.executor().run_until_parked();
16364
16365    cx.update_editor(|editor, _, _| {
16366        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16367        {
16368            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16369        } else {
16370            panic!("expected completion menu to be open");
16371        }
16372    });
16373
16374    cx.update_editor(|editor, window, cx| {
16375        editor.move_page_down(&MovePageDown::default(), window, cx);
16376        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16377        {
16378            assert!(
16379                menu.selected_item == 1,
16380                "expected PageDown to select the last item from the context menu"
16381            );
16382        } else {
16383            panic!("expected completion menu to stay open after PageDown");
16384        }
16385    });
16386
16387    cx.update_editor(|editor, window, cx| {
16388        editor.move_page_up(&MovePageUp::default(), window, cx);
16389        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16390        {
16391            assert!(
16392                menu.selected_item == 0,
16393                "expected PageUp to select the first item from the context menu"
16394            );
16395        } else {
16396            panic!("expected completion menu to stay open after PageUp");
16397        }
16398    });
16399}
16400
16401#[gpui::test]
16402async fn test_as_is_completions(cx: &mut TestAppContext) {
16403    init_test(cx, |_| {});
16404    let mut cx = EditorLspTestContext::new_rust(
16405        lsp::ServerCapabilities {
16406            completion_provider: Some(lsp::CompletionOptions {
16407                ..Default::default()
16408            }),
16409            ..Default::default()
16410        },
16411        cx,
16412    )
16413    .await;
16414    cx.lsp
16415        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16416            Ok(Some(lsp::CompletionResponse::Array(vec![
16417                lsp::CompletionItem {
16418                    label: "unsafe".into(),
16419                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16420                        range: lsp::Range {
16421                            start: lsp::Position {
16422                                line: 1,
16423                                character: 2,
16424                            },
16425                            end: lsp::Position {
16426                                line: 1,
16427                                character: 3,
16428                            },
16429                        },
16430                        new_text: "unsafe".to_string(),
16431                    })),
16432                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16433                    ..Default::default()
16434                },
16435            ])))
16436        });
16437    cx.set_state("fn a() {}\n");
16438    cx.executor().run_until_parked();
16439    cx.update_editor(|editor, window, cx| {
16440        editor.trigger_completion_on_input("n", true, window, cx)
16441    });
16442    cx.executor().run_until_parked();
16443
16444    cx.update_editor(|editor, window, cx| {
16445        editor.confirm_completion(&Default::default(), window, cx)
16446    });
16447    cx.executor().run_until_parked();
16448    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16449}
16450
16451#[gpui::test]
16452async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16453    init_test(cx, |_| {});
16454    let language =
16455        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16456    let mut cx = EditorLspTestContext::new(
16457        language,
16458        lsp::ServerCapabilities {
16459            completion_provider: Some(lsp::CompletionOptions {
16460                ..lsp::CompletionOptions::default()
16461            }),
16462            ..lsp::ServerCapabilities::default()
16463        },
16464        cx,
16465    )
16466    .await;
16467
16468    cx.set_state(
16469        "#ifndef BAR_H
16470#define BAR_H
16471
16472#include <stdbool.h>
16473
16474int fn_branch(bool do_branch1, bool do_branch2);
16475
16476#endif // BAR_H
16477ˇ",
16478    );
16479    cx.executor().run_until_parked();
16480    cx.update_editor(|editor, window, cx| {
16481        editor.handle_input("#", window, cx);
16482    });
16483    cx.executor().run_until_parked();
16484    cx.update_editor(|editor, window, cx| {
16485        editor.handle_input("i", window, cx);
16486    });
16487    cx.executor().run_until_parked();
16488    cx.update_editor(|editor, window, cx| {
16489        editor.handle_input("n", window, cx);
16490    });
16491    cx.executor().run_until_parked();
16492    cx.assert_editor_state(
16493        "#ifndef BAR_H
16494#define BAR_H
16495
16496#include <stdbool.h>
16497
16498int fn_branch(bool do_branch1, bool do_branch2);
16499
16500#endif // BAR_H
16501#inˇ",
16502    );
16503
16504    cx.lsp
16505        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16506            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16507                is_incomplete: false,
16508                item_defaults: None,
16509                items: vec![lsp::CompletionItem {
16510                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16511                    label_details: Some(lsp::CompletionItemLabelDetails {
16512                        detail: Some("header".to_string()),
16513                        description: None,
16514                    }),
16515                    label: " include".to_string(),
16516                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16517                        range: lsp::Range {
16518                            start: lsp::Position {
16519                                line: 8,
16520                                character: 1,
16521                            },
16522                            end: lsp::Position {
16523                                line: 8,
16524                                character: 1,
16525                            },
16526                        },
16527                        new_text: "include \"$0\"".to_string(),
16528                    })),
16529                    sort_text: Some("40b67681include".to_string()),
16530                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16531                    filter_text: Some("include".to_string()),
16532                    insert_text: Some("include \"$0\"".to_string()),
16533                    ..lsp::CompletionItem::default()
16534                }],
16535            })))
16536        });
16537    cx.update_editor(|editor, window, cx| {
16538        editor.show_completions(&ShowCompletions, window, cx);
16539    });
16540    cx.executor().run_until_parked();
16541    cx.update_editor(|editor, window, cx| {
16542        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16543    });
16544    cx.executor().run_until_parked();
16545    cx.assert_editor_state(
16546        "#ifndef BAR_H
16547#define BAR_H
16548
16549#include <stdbool.h>
16550
16551int fn_branch(bool do_branch1, bool do_branch2);
16552
16553#endif // BAR_H
16554#include \"ˇ\"",
16555    );
16556
16557    cx.lsp
16558        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16559            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16560                is_incomplete: true,
16561                item_defaults: None,
16562                items: vec![lsp::CompletionItem {
16563                    kind: Some(lsp::CompletionItemKind::FILE),
16564                    label: "AGL/".to_string(),
16565                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16566                        range: lsp::Range {
16567                            start: lsp::Position {
16568                                line: 8,
16569                                character: 10,
16570                            },
16571                            end: lsp::Position {
16572                                line: 8,
16573                                character: 11,
16574                            },
16575                        },
16576                        new_text: "AGL/".to_string(),
16577                    })),
16578                    sort_text: Some("40b67681AGL/".to_string()),
16579                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16580                    filter_text: Some("AGL/".to_string()),
16581                    insert_text: Some("AGL/".to_string()),
16582                    ..lsp::CompletionItem::default()
16583                }],
16584            })))
16585        });
16586    cx.update_editor(|editor, window, cx| {
16587        editor.show_completions(&ShowCompletions, window, cx);
16588    });
16589    cx.executor().run_until_parked();
16590    cx.update_editor(|editor, window, cx| {
16591        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16592    });
16593    cx.executor().run_until_parked();
16594    cx.assert_editor_state(
16595        r##"#ifndef BAR_H
16596#define BAR_H
16597
16598#include <stdbool.h>
16599
16600int fn_branch(bool do_branch1, bool do_branch2);
16601
16602#endif // BAR_H
16603#include "AGL/ˇ"##,
16604    );
16605
16606    cx.update_editor(|editor, window, cx| {
16607        editor.handle_input("\"", window, cx);
16608    });
16609    cx.executor().run_until_parked();
16610    cx.assert_editor_state(
16611        r##"#ifndef BAR_H
16612#define BAR_H
16613
16614#include <stdbool.h>
16615
16616int fn_branch(bool do_branch1, bool do_branch2);
16617
16618#endif // BAR_H
16619#include "AGL/"ˇ"##,
16620    );
16621}
16622
16623#[gpui::test]
16624async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16625    init_test(cx, |_| {});
16626
16627    let mut cx = EditorLspTestContext::new_rust(
16628        lsp::ServerCapabilities {
16629            completion_provider: Some(lsp::CompletionOptions {
16630                trigger_characters: Some(vec![".".to_string()]),
16631                resolve_provider: Some(false),
16632                ..lsp::CompletionOptions::default()
16633            }),
16634            ..lsp::ServerCapabilities::default()
16635        },
16636        cx,
16637    )
16638    .await;
16639
16640    cx.set_state("fn main() { let a = 2ˇ; }");
16641    cx.simulate_keystroke(".");
16642    let completion_item = lsp::CompletionItem {
16643        label: "Some".into(),
16644        kind: Some(lsp::CompletionItemKind::SNIPPET),
16645        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16646        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16647            kind: lsp::MarkupKind::Markdown,
16648            value: "```rust\nSome(2)\n```".to_string(),
16649        })),
16650        deprecated: Some(false),
16651        sort_text: Some("Some".to_string()),
16652        filter_text: Some("Some".to_string()),
16653        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16654        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16655            range: lsp::Range {
16656                start: lsp::Position {
16657                    line: 0,
16658                    character: 22,
16659                },
16660                end: lsp::Position {
16661                    line: 0,
16662                    character: 22,
16663                },
16664            },
16665            new_text: "Some(2)".to_string(),
16666        })),
16667        additional_text_edits: Some(vec![lsp::TextEdit {
16668            range: lsp::Range {
16669                start: lsp::Position {
16670                    line: 0,
16671                    character: 20,
16672                },
16673                end: lsp::Position {
16674                    line: 0,
16675                    character: 22,
16676                },
16677            },
16678            new_text: "".to_string(),
16679        }]),
16680        ..Default::default()
16681    };
16682
16683    let closure_completion_item = completion_item.clone();
16684    let counter = Arc::new(AtomicUsize::new(0));
16685    let counter_clone = counter.clone();
16686    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16687        let task_completion_item = closure_completion_item.clone();
16688        counter_clone.fetch_add(1, atomic::Ordering::Release);
16689        async move {
16690            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16691                is_incomplete: true,
16692                item_defaults: None,
16693                items: vec![task_completion_item],
16694            })))
16695        }
16696    });
16697
16698    cx.condition(|editor, _| editor.context_menu_visible())
16699        .await;
16700    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16701    assert!(request.next().await.is_some());
16702    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16703
16704    cx.simulate_keystrokes("S o m");
16705    cx.condition(|editor, _| editor.context_menu_visible())
16706        .await;
16707    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16708    assert!(request.next().await.is_some());
16709    assert!(request.next().await.is_some());
16710    assert!(request.next().await.is_some());
16711    request.close();
16712    assert!(request.next().await.is_none());
16713    assert_eq!(
16714        counter.load(atomic::Ordering::Acquire),
16715        4,
16716        "With the completions menu open, only one LSP request should happen per input"
16717    );
16718}
16719
16720#[gpui::test]
16721async fn test_toggle_comment(cx: &mut TestAppContext) {
16722    init_test(cx, |_| {});
16723    let mut cx = EditorTestContext::new(cx).await;
16724    let language = Arc::new(Language::new(
16725        LanguageConfig {
16726            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16727            ..Default::default()
16728        },
16729        Some(tree_sitter_rust::LANGUAGE.into()),
16730    ));
16731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16732
16733    // If multiple selections intersect a line, the line is only toggled once.
16734    cx.set_state(indoc! {"
16735        fn a() {
16736            «//b();
16737            ˇ»// «c();
16738            //ˇ»  d();
16739        }
16740    "});
16741
16742    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16743
16744    cx.assert_editor_state(indoc! {"
16745        fn a() {
16746            «b();
16747            ˇ»«c();
16748            ˇ» d();
16749        }
16750    "});
16751
16752    // The comment prefix is inserted at the same column for every line in a
16753    // selection.
16754    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16755
16756    cx.assert_editor_state(indoc! {"
16757        fn a() {
16758            // «b();
16759            ˇ»// «c();
16760            ˇ» // d();
16761        }
16762    "});
16763
16764    // If a selection ends at the beginning of a line, that line is not toggled.
16765    cx.set_selections_state(indoc! {"
16766        fn a() {
16767            // b();
16768            «// c();
16769        ˇ»     // d();
16770        }
16771    "});
16772
16773    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16774
16775    cx.assert_editor_state(indoc! {"
16776        fn a() {
16777            // b();
16778            «c();
16779        ˇ»     // d();
16780        }
16781    "});
16782
16783    // If a selection span a single line and is empty, the line is toggled.
16784    cx.set_state(indoc! {"
16785        fn a() {
16786            a();
16787            b();
16788        ˇ
16789        }
16790    "});
16791
16792    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16793
16794    cx.assert_editor_state(indoc! {"
16795        fn a() {
16796            a();
16797            b();
16798        //•ˇ
16799        }
16800    "});
16801
16802    // If a selection span multiple lines, empty lines are not toggled.
16803    cx.set_state(indoc! {"
16804        fn a() {
16805            «a();
16806
16807            c();ˇ»
16808        }
16809    "});
16810
16811    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16812
16813    cx.assert_editor_state(indoc! {"
16814        fn a() {
16815            // «a();
16816
16817            // c();ˇ»
16818        }
16819    "});
16820
16821    // If a selection includes multiple comment prefixes, all lines are uncommented.
16822    cx.set_state(indoc! {"
16823        fn a() {
16824            «// a();
16825            /// b();
16826            //! c();ˇ»
16827        }
16828    "});
16829
16830    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16831
16832    cx.assert_editor_state(indoc! {"
16833        fn a() {
16834            «a();
16835            b();
16836            c();ˇ»
16837        }
16838    "});
16839}
16840
16841#[gpui::test]
16842async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16843    init_test(cx, |_| {});
16844    let mut cx = EditorTestContext::new(cx).await;
16845    let language = Arc::new(Language::new(
16846        LanguageConfig {
16847            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16848            ..Default::default()
16849        },
16850        Some(tree_sitter_rust::LANGUAGE.into()),
16851    ));
16852    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16853
16854    let toggle_comments = &ToggleComments {
16855        advance_downwards: false,
16856        ignore_indent: true,
16857    };
16858
16859    // If multiple selections intersect a line, the line is only toggled once.
16860    cx.set_state(indoc! {"
16861        fn a() {
16862        //    «b();
16863        //    c();
16864        //    ˇ» d();
16865        }
16866    "});
16867
16868    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16869
16870    cx.assert_editor_state(indoc! {"
16871        fn a() {
16872            «b();
16873            c();
16874            ˇ» d();
16875        }
16876    "});
16877
16878    // The comment prefix is inserted at the beginning of each line
16879    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16880
16881    cx.assert_editor_state(indoc! {"
16882        fn a() {
16883        //    «b();
16884        //    c();
16885        //    ˇ» d();
16886        }
16887    "});
16888
16889    // If a selection ends at the beginning of a line, that line is not toggled.
16890    cx.set_selections_state(indoc! {"
16891        fn a() {
16892        //    b();
16893        //    «c();
16894        ˇ»//     d();
16895        }
16896    "});
16897
16898    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16899
16900    cx.assert_editor_state(indoc! {"
16901        fn a() {
16902        //    b();
16903            «c();
16904        ˇ»//     d();
16905        }
16906    "});
16907
16908    // If a selection span a single line and is empty, the line is toggled.
16909    cx.set_state(indoc! {"
16910        fn a() {
16911            a();
16912            b();
16913        ˇ
16914        }
16915    "});
16916
16917    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16918
16919    cx.assert_editor_state(indoc! {"
16920        fn a() {
16921            a();
16922            b();
16923        //ˇ
16924        }
16925    "});
16926
16927    // If a selection span multiple lines, empty lines are not toggled.
16928    cx.set_state(indoc! {"
16929        fn a() {
16930            «a();
16931
16932            c();ˇ»
16933        }
16934    "});
16935
16936    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16937
16938    cx.assert_editor_state(indoc! {"
16939        fn a() {
16940        //    «a();
16941
16942        //    c();ˇ»
16943        }
16944    "});
16945
16946    // If a selection includes multiple comment prefixes, all lines are uncommented.
16947    cx.set_state(indoc! {"
16948        fn a() {
16949        //    «a();
16950        ///    b();
16951        //!    c();ˇ»
16952        }
16953    "});
16954
16955    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16956
16957    cx.assert_editor_state(indoc! {"
16958        fn a() {
16959            «a();
16960            b();
16961            c();ˇ»
16962        }
16963    "});
16964}
16965
16966#[gpui::test]
16967async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16968    init_test(cx, |_| {});
16969
16970    let language = Arc::new(Language::new(
16971        LanguageConfig {
16972            line_comments: vec!["// ".into()],
16973            ..Default::default()
16974        },
16975        Some(tree_sitter_rust::LANGUAGE.into()),
16976    ));
16977
16978    let mut cx = EditorTestContext::new(cx).await;
16979
16980    cx.language_registry().add(language.clone());
16981    cx.update_buffer(|buffer, cx| {
16982        buffer.set_language(Some(language), cx);
16983    });
16984
16985    let toggle_comments = &ToggleComments {
16986        advance_downwards: true,
16987        ignore_indent: false,
16988    };
16989
16990    // Single cursor on one line -> advance
16991    // Cursor moves horizontally 3 characters as well on non-blank line
16992    cx.set_state(indoc!(
16993        "fn a() {
16994             ˇdog();
16995             cat();
16996        }"
16997    ));
16998    cx.update_editor(|editor, window, cx| {
16999        editor.toggle_comments(toggle_comments, window, cx);
17000    });
17001    cx.assert_editor_state(indoc!(
17002        "fn a() {
17003             // dog();
17004             catˇ();
17005        }"
17006    ));
17007
17008    // Single selection on one line -> don't advance
17009    cx.set_state(indoc!(
17010        "fn a() {
17011             «dog()ˇ»;
17012             cat();
17013        }"
17014    ));
17015    cx.update_editor(|editor, window, cx| {
17016        editor.toggle_comments(toggle_comments, window, cx);
17017    });
17018    cx.assert_editor_state(indoc!(
17019        "fn a() {
17020             // «dog()ˇ»;
17021             cat();
17022        }"
17023    ));
17024
17025    // Multiple cursors on one line -> advance
17026    cx.set_state(indoc!(
17027        "fn a() {
17028             ˇdˇog();
17029             cat();
17030        }"
17031    ));
17032    cx.update_editor(|editor, window, cx| {
17033        editor.toggle_comments(toggle_comments, window, cx);
17034    });
17035    cx.assert_editor_state(indoc!(
17036        "fn a() {
17037             // dog();
17038             catˇ(ˇ);
17039        }"
17040    ));
17041
17042    // Multiple cursors on one line, with selection -> don't advance
17043    cx.set_state(indoc!(
17044        "fn a() {
17045             ˇdˇog«()ˇ»;
17046             cat();
17047        }"
17048    ));
17049    cx.update_editor(|editor, window, cx| {
17050        editor.toggle_comments(toggle_comments, window, cx);
17051    });
17052    cx.assert_editor_state(indoc!(
17053        "fn a() {
17054             // ˇdˇog«()ˇ»;
17055             cat();
17056        }"
17057    ));
17058
17059    // Single cursor on one line -> advance
17060    // Cursor moves to column 0 on blank line
17061    cx.set_state(indoc!(
17062        "fn a() {
17063             ˇdog();
17064
17065             cat();
17066        }"
17067    ));
17068    cx.update_editor(|editor, window, cx| {
17069        editor.toggle_comments(toggle_comments, window, cx);
17070    });
17071    cx.assert_editor_state(indoc!(
17072        "fn a() {
17073             // dog();
17074        ˇ
17075             cat();
17076        }"
17077    ));
17078
17079    // Single cursor on one line -> advance
17080    // Cursor starts and ends at column 0
17081    cx.set_state(indoc!(
17082        "fn a() {
17083         ˇ    dog();
17084             cat();
17085        }"
17086    ));
17087    cx.update_editor(|editor, window, cx| {
17088        editor.toggle_comments(toggle_comments, window, cx);
17089    });
17090    cx.assert_editor_state(indoc!(
17091        "fn a() {
17092             // dog();
17093         ˇ    cat();
17094        }"
17095    ));
17096}
17097
17098#[gpui::test]
17099async fn test_toggle_block_comment(cx: &mut TestAppContext) {
17100    init_test(cx, |_| {});
17101
17102    let mut cx = EditorTestContext::new(cx).await;
17103
17104    let html_language = Arc::new(
17105        Language::new(
17106            LanguageConfig {
17107                name: "HTML".into(),
17108                block_comment: Some(BlockCommentConfig {
17109                    start: "<!-- ".into(),
17110                    prefix: "".into(),
17111                    end: " -->".into(),
17112                    tab_size: 0,
17113                }),
17114                ..Default::default()
17115            },
17116            Some(tree_sitter_html::LANGUAGE.into()),
17117        )
17118        .with_injection_query(
17119            r#"
17120            (script_element
17121                (raw_text) @injection.content
17122                (#set! injection.language "javascript"))
17123            "#,
17124        )
17125        .unwrap(),
17126    );
17127
17128    let javascript_language = Arc::new(Language::new(
17129        LanguageConfig {
17130            name: "JavaScript".into(),
17131            line_comments: vec!["// ".into()],
17132            ..Default::default()
17133        },
17134        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17135    ));
17136
17137    cx.language_registry().add(html_language.clone());
17138    cx.language_registry().add(javascript_language);
17139    cx.update_buffer(|buffer, cx| {
17140        buffer.set_language(Some(html_language), cx);
17141    });
17142
17143    // Toggle comments for empty selections
17144    cx.set_state(
17145        &r#"
17146            <p>A</p>ˇ
17147            <p>B</p>ˇ
17148            <p>C</p>ˇ
17149        "#
17150        .unindent(),
17151    );
17152    cx.update_editor(|editor, window, cx| {
17153        editor.toggle_comments(&ToggleComments::default(), window, cx)
17154    });
17155    cx.assert_editor_state(
17156        &r#"
17157            <!-- <p>A</p>ˇ -->
17158            <!-- <p>B</p>ˇ -->
17159            <!-- <p>C</p>ˇ -->
17160        "#
17161        .unindent(),
17162    );
17163    cx.update_editor(|editor, window, cx| {
17164        editor.toggle_comments(&ToggleComments::default(), window, cx)
17165    });
17166    cx.assert_editor_state(
17167        &r#"
17168            <p>A</p>ˇ
17169            <p>B</p>ˇ
17170            <p>C</p>ˇ
17171        "#
17172        .unindent(),
17173    );
17174
17175    // Toggle comments for mixture of empty and non-empty selections, where
17176    // multiple selections occupy a given line.
17177    cx.set_state(
17178        &r#"
17179            <p>A«</p>
17180            <p>ˇ»B</p>ˇ
17181            <p>C«</p>
17182            <p>ˇ»D</p>ˇ
17183        "#
17184        .unindent(),
17185    );
17186
17187    cx.update_editor(|editor, window, cx| {
17188        editor.toggle_comments(&ToggleComments::default(), window, cx)
17189    });
17190    cx.assert_editor_state(
17191        &r#"
17192            <!-- <p>A«</p>
17193            <p>ˇ»B</p>ˇ -->
17194            <!-- <p>C«</p>
17195            <p>ˇ»D</p>ˇ -->
17196        "#
17197        .unindent(),
17198    );
17199    cx.update_editor(|editor, window, cx| {
17200        editor.toggle_comments(&ToggleComments::default(), window, cx)
17201    });
17202    cx.assert_editor_state(
17203        &r#"
17204            <p>A«</p>
17205            <p>ˇ»B</p>ˇ
17206            <p>C«</p>
17207            <p>ˇ»D</p>ˇ
17208        "#
17209        .unindent(),
17210    );
17211
17212    // Toggle comments when different languages are active for different
17213    // selections.
17214    cx.set_state(
17215        &r#"
17216            ˇ<script>
17217                ˇvar x = new Y();
17218            ˇ</script>
17219        "#
17220        .unindent(),
17221    );
17222    cx.executor().run_until_parked();
17223    cx.update_editor(|editor, window, cx| {
17224        editor.toggle_comments(&ToggleComments::default(), window, cx)
17225    });
17226    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
17227    // Uncommenting and commenting from this position brings in even more wrong artifacts.
17228    cx.assert_editor_state(
17229        &r#"
17230            <!-- ˇ<script> -->
17231                // ˇvar x = new Y();
17232            <!-- ˇ</script> -->
17233        "#
17234        .unindent(),
17235    );
17236}
17237
17238#[gpui::test]
17239fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
17240    init_test(cx, |_| {});
17241
17242    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17243    let multibuffer = cx.new(|cx| {
17244        let mut multibuffer = MultiBuffer::new(ReadWrite);
17245        multibuffer.push_excerpts(
17246            buffer.clone(),
17247            [
17248                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
17249                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
17250            ],
17251            cx,
17252        );
17253        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
17254        multibuffer
17255    });
17256
17257    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17258    editor.update_in(cx, |editor, window, cx| {
17259        assert_eq!(editor.text(cx), "aaaa\nbbbb");
17260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17261            s.select_ranges([
17262                Point::new(0, 0)..Point::new(0, 0),
17263                Point::new(1, 0)..Point::new(1, 0),
17264            ])
17265        });
17266
17267        editor.handle_input("X", window, cx);
17268        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
17269        assert_eq!(
17270            editor.selections.ranges(&editor.display_snapshot(cx)),
17271            [
17272                Point::new(0, 1)..Point::new(0, 1),
17273                Point::new(1, 1)..Point::new(1, 1),
17274            ]
17275        );
17276
17277        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17278        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17279            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17280        });
17281        editor.backspace(&Default::default(), window, cx);
17282        assert_eq!(editor.text(cx), "Xa\nbbb");
17283        assert_eq!(
17284            editor.selections.ranges(&editor.display_snapshot(cx)),
17285            [Point::new(1, 0)..Point::new(1, 0)]
17286        );
17287
17288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17289            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17290        });
17291        editor.backspace(&Default::default(), window, cx);
17292        assert_eq!(editor.text(cx), "X\nbb");
17293        assert_eq!(
17294            editor.selections.ranges(&editor.display_snapshot(cx)),
17295            [Point::new(0, 1)..Point::new(0, 1)]
17296        );
17297    });
17298}
17299
17300#[gpui::test]
17301fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17302    init_test(cx, |_| {});
17303
17304    let markers = vec![('[', ']').into(), ('(', ')').into()];
17305    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17306        indoc! {"
17307            [aaaa
17308            (bbbb]
17309            cccc)",
17310        },
17311        markers.clone(),
17312    );
17313    let excerpt_ranges = markers.into_iter().map(|marker| {
17314        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17315        ExcerptRange::new(context)
17316    });
17317    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17318    let multibuffer = cx.new(|cx| {
17319        let mut multibuffer = MultiBuffer::new(ReadWrite);
17320        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17321        multibuffer
17322    });
17323
17324    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17325    editor.update_in(cx, |editor, window, cx| {
17326        let (expected_text, selection_ranges) = marked_text_ranges(
17327            indoc! {"
17328                aaaa
17329                bˇbbb
17330                bˇbbˇb
17331                cccc"
17332            },
17333            true,
17334        );
17335        assert_eq!(editor.text(cx), expected_text);
17336        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17337            s.select_ranges(
17338                selection_ranges
17339                    .iter()
17340                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17341            )
17342        });
17343
17344        editor.handle_input("X", window, cx);
17345
17346        let (expected_text, expected_selections) = marked_text_ranges(
17347            indoc! {"
17348                aaaa
17349                bXˇbbXb
17350                bXˇbbXˇb
17351                cccc"
17352            },
17353            false,
17354        );
17355        assert_eq!(editor.text(cx), expected_text);
17356        assert_eq!(
17357            editor.selections.ranges(&editor.display_snapshot(cx)),
17358            expected_selections
17359                .iter()
17360                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17361                .collect::<Vec<_>>()
17362        );
17363
17364        editor.newline(&Newline, window, cx);
17365        let (expected_text, expected_selections) = marked_text_ranges(
17366            indoc! {"
17367                aaaa
17368                bX
17369                ˇbbX
17370                b
17371                bX
17372                ˇbbX
17373                ˇb
17374                cccc"
17375            },
17376            false,
17377        );
17378        assert_eq!(editor.text(cx), expected_text);
17379        assert_eq!(
17380            editor.selections.ranges(&editor.display_snapshot(cx)),
17381            expected_selections
17382                .iter()
17383                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17384                .collect::<Vec<_>>()
17385        );
17386    });
17387}
17388
17389#[gpui::test]
17390fn test_refresh_selections(cx: &mut TestAppContext) {
17391    init_test(cx, |_| {});
17392
17393    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17394    let mut excerpt1_id = None;
17395    let multibuffer = cx.new(|cx| {
17396        let mut multibuffer = MultiBuffer::new(ReadWrite);
17397        excerpt1_id = multibuffer
17398            .push_excerpts(
17399                buffer.clone(),
17400                [
17401                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17402                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17403                ],
17404                cx,
17405            )
17406            .into_iter()
17407            .next();
17408        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17409        multibuffer
17410    });
17411
17412    let editor = cx.add_window(|window, cx| {
17413        let mut editor = build_editor(multibuffer.clone(), window, cx);
17414        let snapshot = editor.snapshot(window, cx);
17415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17416            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17417        });
17418        editor.begin_selection(
17419            Point::new(2, 1).to_display_point(&snapshot),
17420            true,
17421            1,
17422            window,
17423            cx,
17424        );
17425        assert_eq!(
17426            editor.selections.ranges(&editor.display_snapshot(cx)),
17427            [
17428                Point::new(1, 3)..Point::new(1, 3),
17429                Point::new(2, 1)..Point::new(2, 1),
17430            ]
17431        );
17432        editor
17433    });
17434
17435    // Refreshing selections is a no-op when excerpts haven't changed.
17436    _ = editor.update(cx, |editor, window, cx| {
17437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17438        assert_eq!(
17439            editor.selections.ranges(&editor.display_snapshot(cx)),
17440            [
17441                Point::new(1, 3)..Point::new(1, 3),
17442                Point::new(2, 1)..Point::new(2, 1),
17443            ]
17444        );
17445    });
17446
17447    multibuffer.update(cx, |multibuffer, cx| {
17448        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17449    });
17450    _ = editor.update(cx, |editor, window, cx| {
17451        // Removing an excerpt causes the first selection to become degenerate.
17452        assert_eq!(
17453            editor.selections.ranges(&editor.display_snapshot(cx)),
17454            [
17455                Point::new(0, 0)..Point::new(0, 0),
17456                Point::new(0, 1)..Point::new(0, 1)
17457            ]
17458        );
17459
17460        // Refreshing selections will relocate the first selection to the original buffer
17461        // location.
17462        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17463        assert_eq!(
17464            editor.selections.ranges(&editor.display_snapshot(cx)),
17465            [
17466                Point::new(0, 1)..Point::new(0, 1),
17467                Point::new(0, 3)..Point::new(0, 3)
17468            ]
17469        );
17470        assert!(editor.selections.pending_anchor().is_some());
17471    });
17472}
17473
17474#[gpui::test]
17475fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17476    init_test(cx, |_| {});
17477
17478    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17479    let mut excerpt1_id = None;
17480    let multibuffer = cx.new(|cx| {
17481        let mut multibuffer = MultiBuffer::new(ReadWrite);
17482        excerpt1_id = multibuffer
17483            .push_excerpts(
17484                buffer.clone(),
17485                [
17486                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17487                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17488                ],
17489                cx,
17490            )
17491            .into_iter()
17492            .next();
17493        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17494        multibuffer
17495    });
17496
17497    let editor = cx.add_window(|window, cx| {
17498        let mut editor = build_editor(multibuffer.clone(), window, cx);
17499        let snapshot = editor.snapshot(window, cx);
17500        editor.begin_selection(
17501            Point::new(1, 3).to_display_point(&snapshot),
17502            false,
17503            1,
17504            window,
17505            cx,
17506        );
17507        assert_eq!(
17508            editor.selections.ranges(&editor.display_snapshot(cx)),
17509            [Point::new(1, 3)..Point::new(1, 3)]
17510        );
17511        editor
17512    });
17513
17514    multibuffer.update(cx, |multibuffer, cx| {
17515        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17516    });
17517    _ = editor.update(cx, |editor, window, cx| {
17518        assert_eq!(
17519            editor.selections.ranges(&editor.display_snapshot(cx)),
17520            [Point::new(0, 0)..Point::new(0, 0)]
17521        );
17522
17523        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17525        assert_eq!(
17526            editor.selections.ranges(&editor.display_snapshot(cx)),
17527            [Point::new(0, 3)..Point::new(0, 3)]
17528        );
17529        assert!(editor.selections.pending_anchor().is_some());
17530    });
17531}
17532
17533#[gpui::test]
17534async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17535    init_test(cx, |_| {});
17536
17537    let language = Arc::new(
17538        Language::new(
17539            LanguageConfig {
17540                brackets: BracketPairConfig {
17541                    pairs: vec![
17542                        BracketPair {
17543                            start: "{".to_string(),
17544                            end: "}".to_string(),
17545                            close: true,
17546                            surround: true,
17547                            newline: true,
17548                        },
17549                        BracketPair {
17550                            start: "/* ".to_string(),
17551                            end: " */".to_string(),
17552                            close: true,
17553                            surround: true,
17554                            newline: true,
17555                        },
17556                    ],
17557                    ..Default::default()
17558                },
17559                ..Default::default()
17560            },
17561            Some(tree_sitter_rust::LANGUAGE.into()),
17562        )
17563        .with_indents_query("")
17564        .unwrap(),
17565    );
17566
17567    let text = concat!(
17568        "{   }\n",     //
17569        "  x\n",       //
17570        "  /*   */\n", //
17571        "x\n",         //
17572        "{{} }\n",     //
17573    );
17574
17575    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17576    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17577    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17578    editor
17579        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17580        .await;
17581
17582    editor.update_in(cx, |editor, window, cx| {
17583        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17584            s.select_display_ranges([
17585                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17586                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17587                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17588            ])
17589        });
17590        editor.newline(&Newline, window, cx);
17591
17592        assert_eq!(
17593            editor.buffer().read(cx).read(cx).text(),
17594            concat!(
17595                "{ \n",    // Suppress rustfmt
17596                "\n",      //
17597                "}\n",     //
17598                "  x\n",   //
17599                "  /* \n", //
17600                "  \n",    //
17601                "  */\n",  //
17602                "x\n",     //
17603                "{{} \n",  //
17604                "}\n",     //
17605            )
17606        );
17607    });
17608}
17609
17610#[gpui::test]
17611fn test_highlighted_ranges(cx: &mut TestAppContext) {
17612    init_test(cx, |_| {});
17613
17614    let editor = cx.add_window(|window, cx| {
17615        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17616        build_editor(buffer, window, cx)
17617    });
17618
17619    _ = editor.update(cx, |editor, window, cx| {
17620        let buffer = editor.buffer.read(cx).snapshot(cx);
17621
17622        let anchor_range =
17623            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17624
17625        editor.highlight_background(
17626            HighlightKey::ColorizeBracket(0),
17627            &[
17628                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17629                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17630                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17631                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17632            ],
17633            |_, _| Hsla::red(),
17634            cx,
17635        );
17636        editor.highlight_background(
17637            HighlightKey::ColorizeBracket(1),
17638            &[
17639                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17640                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17641                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17642                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17643            ],
17644            |_, _| Hsla::green(),
17645            cx,
17646        );
17647
17648        let snapshot = editor.snapshot(window, cx);
17649        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17650            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17651            &snapshot,
17652            cx.theme(),
17653        );
17654        assert_eq!(
17655            highlighted_ranges,
17656            &[
17657                (
17658                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17659                    Hsla::green(),
17660                ),
17661                (
17662                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17663                    Hsla::red(),
17664                ),
17665                (
17666                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17667                    Hsla::green(),
17668                ),
17669                (
17670                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17671                    Hsla::red(),
17672                ),
17673            ]
17674        );
17675        assert_eq!(
17676            editor.sorted_background_highlights_in_range(
17677                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17678                &snapshot,
17679                cx.theme(),
17680            ),
17681            &[(
17682                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17683                Hsla::red(),
17684            )]
17685        );
17686    });
17687}
17688
17689#[gpui::test]
17690async fn test_following(cx: &mut TestAppContext) {
17691    init_test(cx, |_| {});
17692
17693    let fs = FakeFs::new(cx.executor());
17694    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17695
17696    let buffer = project.update(cx, |project, cx| {
17697        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17698        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17699    });
17700    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17701    let follower = cx.update(|cx| {
17702        cx.open_window(
17703            WindowOptions {
17704                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17705                    gpui::Point::new(px(0.), px(0.)),
17706                    gpui::Point::new(px(10.), px(80.)),
17707                ))),
17708                ..Default::default()
17709            },
17710            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17711        )
17712        .unwrap()
17713    });
17714
17715    let is_still_following = Rc::new(RefCell::new(true));
17716    let follower_edit_event_count = Rc::new(RefCell::new(0));
17717    let pending_update = Rc::new(RefCell::new(None));
17718    let leader_entity = leader.root(cx).unwrap();
17719    let follower_entity = follower.root(cx).unwrap();
17720    _ = follower.update(cx, {
17721        let update = pending_update.clone();
17722        let is_still_following = is_still_following.clone();
17723        let follower_edit_event_count = follower_edit_event_count.clone();
17724        |_, window, cx| {
17725            cx.subscribe_in(
17726                &leader_entity,
17727                window,
17728                move |_, leader, event, window, cx| {
17729                    leader.update(cx, |leader, cx| {
17730                        leader.add_event_to_update_proto(
17731                            event,
17732                            &mut update.borrow_mut(),
17733                            window,
17734                            cx,
17735                        );
17736                    });
17737                },
17738            )
17739            .detach();
17740
17741            cx.subscribe_in(
17742                &follower_entity,
17743                window,
17744                move |_, _, event: &EditorEvent, _window, _cx| {
17745                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17746                        *is_still_following.borrow_mut() = false;
17747                    }
17748
17749                    if let EditorEvent::BufferEdited = event {
17750                        *follower_edit_event_count.borrow_mut() += 1;
17751                    }
17752                },
17753            )
17754            .detach();
17755        }
17756    });
17757
17758    // Update the selections only
17759    _ = leader.update(cx, |leader, window, cx| {
17760        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17761            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17762        });
17763    });
17764    follower
17765        .update(cx, |follower, window, cx| {
17766            follower.apply_update_proto(
17767                &project,
17768                pending_update.borrow_mut().take().unwrap(),
17769                window,
17770                cx,
17771            )
17772        })
17773        .unwrap()
17774        .await
17775        .unwrap();
17776    _ = follower.update(cx, |follower, _, cx| {
17777        assert_eq!(
17778            follower.selections.ranges(&follower.display_snapshot(cx)),
17779            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17780        );
17781    });
17782    assert!(*is_still_following.borrow());
17783    assert_eq!(*follower_edit_event_count.borrow(), 0);
17784
17785    // Update the scroll position only
17786    _ = leader.update(cx, |leader, window, cx| {
17787        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17788    });
17789    follower
17790        .update(cx, |follower, window, cx| {
17791            follower.apply_update_proto(
17792                &project,
17793                pending_update.borrow_mut().take().unwrap(),
17794                window,
17795                cx,
17796            )
17797        })
17798        .unwrap()
17799        .await
17800        .unwrap();
17801    assert_eq!(
17802        follower
17803            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17804            .unwrap(),
17805        gpui::Point::new(1.5, 3.5)
17806    );
17807    assert!(*is_still_following.borrow());
17808    assert_eq!(*follower_edit_event_count.borrow(), 0);
17809
17810    // Update the selections and scroll position. The follower's scroll position is updated
17811    // via autoscroll, not via the leader's exact scroll position.
17812    _ = leader.update(cx, |leader, window, cx| {
17813        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17814            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17815        });
17816        leader.request_autoscroll(Autoscroll::newest(), cx);
17817        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17818    });
17819    follower
17820        .update(cx, |follower, window, cx| {
17821            follower.apply_update_proto(
17822                &project,
17823                pending_update.borrow_mut().take().unwrap(),
17824                window,
17825                cx,
17826            )
17827        })
17828        .unwrap()
17829        .await
17830        .unwrap();
17831    _ = follower.update(cx, |follower, _, cx| {
17832        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17833        assert_eq!(
17834            follower.selections.ranges(&follower.display_snapshot(cx)),
17835            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17836        );
17837    });
17838    assert!(*is_still_following.borrow());
17839
17840    // Creating a pending selection that precedes another selection
17841    _ = leader.update(cx, |leader, window, cx| {
17842        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17843            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17844        });
17845        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17846    });
17847    follower
17848        .update(cx, |follower, window, cx| {
17849            follower.apply_update_proto(
17850                &project,
17851                pending_update.borrow_mut().take().unwrap(),
17852                window,
17853                cx,
17854            )
17855        })
17856        .unwrap()
17857        .await
17858        .unwrap();
17859    _ = follower.update(cx, |follower, _, cx| {
17860        assert_eq!(
17861            follower.selections.ranges(&follower.display_snapshot(cx)),
17862            vec![
17863                MultiBufferOffset(0)..MultiBufferOffset(0),
17864                MultiBufferOffset(1)..MultiBufferOffset(1)
17865            ]
17866        );
17867    });
17868    assert!(*is_still_following.borrow());
17869
17870    // Extend the pending selection so that it surrounds another selection
17871    _ = leader.update(cx, |leader, window, cx| {
17872        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17873    });
17874    follower
17875        .update(cx, |follower, window, cx| {
17876            follower.apply_update_proto(
17877                &project,
17878                pending_update.borrow_mut().take().unwrap(),
17879                window,
17880                cx,
17881            )
17882        })
17883        .unwrap()
17884        .await
17885        .unwrap();
17886    _ = follower.update(cx, |follower, _, cx| {
17887        assert_eq!(
17888            follower.selections.ranges(&follower.display_snapshot(cx)),
17889            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17890        );
17891    });
17892
17893    // Scrolling locally breaks the follow
17894    _ = follower.update(cx, |follower, window, cx| {
17895        let top_anchor = follower
17896            .buffer()
17897            .read(cx)
17898            .read(cx)
17899            .anchor_after(MultiBufferOffset(0));
17900        follower.set_scroll_anchor(
17901            ScrollAnchor {
17902                anchor: top_anchor,
17903                offset: gpui::Point::new(0.0, 0.5),
17904            },
17905            window,
17906            cx,
17907        );
17908    });
17909    assert!(!(*is_still_following.borrow()));
17910}
17911
17912#[gpui::test]
17913async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17914    init_test(cx, |_| {});
17915
17916    let fs = FakeFs::new(cx.executor());
17917    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17918    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17919    let pane = workspace
17920        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17921        .unwrap();
17922
17923    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17924
17925    let leader = pane.update_in(cx, |_, window, cx| {
17926        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17927        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17928    });
17929
17930    // Start following the editor when it has no excerpts.
17931    let mut state_message =
17932        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17933    let workspace_entity = workspace.root(cx).unwrap();
17934    let follower_1 = cx
17935        .update_window(*workspace.deref(), |_, window, cx| {
17936            Editor::from_state_proto(
17937                workspace_entity,
17938                ViewId {
17939                    creator: CollaboratorId::PeerId(PeerId::default()),
17940                    id: 0,
17941                },
17942                &mut state_message,
17943                window,
17944                cx,
17945            )
17946        })
17947        .unwrap()
17948        .unwrap()
17949        .await
17950        .unwrap();
17951
17952    let update_message = Rc::new(RefCell::new(None));
17953    follower_1.update_in(cx, {
17954        let update = update_message.clone();
17955        |_, window, cx| {
17956            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17957                leader.update(cx, |leader, cx| {
17958                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
17959                });
17960            })
17961            .detach();
17962        }
17963    });
17964
17965    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17966        (
17967            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17968            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17969        )
17970    });
17971
17972    // Insert some excerpts.
17973    leader.update(cx, |leader, cx| {
17974        leader.buffer.update(cx, |multibuffer, cx| {
17975            multibuffer.set_excerpts_for_path(
17976                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17977                buffer_1.clone(),
17978                vec![
17979                    Point::row_range(0..3),
17980                    Point::row_range(1..6),
17981                    Point::row_range(12..15),
17982                ],
17983                0,
17984                cx,
17985            );
17986            multibuffer.set_excerpts_for_path(
17987                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17988                buffer_2.clone(),
17989                vec![Point::row_range(0..6), Point::row_range(8..12)],
17990                0,
17991                cx,
17992            );
17993        });
17994    });
17995
17996    // Apply the update of adding the excerpts.
17997    follower_1
17998        .update_in(cx, |follower, window, cx| {
17999            follower.apply_update_proto(
18000                &project,
18001                update_message.borrow().clone().unwrap(),
18002                window,
18003                cx,
18004            )
18005        })
18006        .await
18007        .unwrap();
18008    assert_eq!(
18009        follower_1.update(cx, |editor, cx| editor.text(cx)),
18010        leader.update(cx, |editor, cx| editor.text(cx))
18011    );
18012    update_message.borrow_mut().take();
18013
18014    // Start following separately after it already has excerpts.
18015    let mut state_message =
18016        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18017    let workspace_entity = workspace.root(cx).unwrap();
18018    let follower_2 = cx
18019        .update_window(*workspace.deref(), |_, window, cx| {
18020            Editor::from_state_proto(
18021                workspace_entity,
18022                ViewId {
18023                    creator: CollaboratorId::PeerId(PeerId::default()),
18024                    id: 0,
18025                },
18026                &mut state_message,
18027                window,
18028                cx,
18029            )
18030        })
18031        .unwrap()
18032        .unwrap()
18033        .await
18034        .unwrap();
18035    assert_eq!(
18036        follower_2.update(cx, |editor, cx| editor.text(cx)),
18037        leader.update(cx, |editor, cx| editor.text(cx))
18038    );
18039
18040    // Remove some excerpts.
18041    leader.update(cx, |leader, cx| {
18042        leader.buffer.update(cx, |multibuffer, cx| {
18043            let excerpt_ids = multibuffer.excerpt_ids();
18044            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
18045            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
18046        });
18047    });
18048
18049    // Apply the update of removing the excerpts.
18050    follower_1
18051        .update_in(cx, |follower, window, cx| {
18052            follower.apply_update_proto(
18053                &project,
18054                update_message.borrow().clone().unwrap(),
18055                window,
18056                cx,
18057            )
18058        })
18059        .await
18060        .unwrap();
18061    follower_2
18062        .update_in(cx, |follower, window, cx| {
18063            follower.apply_update_proto(
18064                &project,
18065                update_message.borrow().clone().unwrap(),
18066                window,
18067                cx,
18068            )
18069        })
18070        .await
18071        .unwrap();
18072    update_message.borrow_mut().take();
18073    assert_eq!(
18074        follower_1.update(cx, |editor, cx| editor.text(cx)),
18075        leader.update(cx, |editor, cx| editor.text(cx))
18076    );
18077}
18078
18079#[gpui::test]
18080async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18081    init_test(cx, |_| {});
18082
18083    let mut cx = EditorTestContext::new(cx).await;
18084    let lsp_store =
18085        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
18086
18087    cx.set_state(indoc! {"
18088        ˇfn func(abc def: i32) -> u32 {
18089        }
18090    "});
18091
18092    cx.update(|_, cx| {
18093        lsp_store.update(cx, |lsp_store, cx| {
18094            lsp_store
18095                .update_diagnostics(
18096                    LanguageServerId(0),
18097                    lsp::PublishDiagnosticsParams {
18098                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
18099                        version: None,
18100                        diagnostics: vec![
18101                            lsp::Diagnostic {
18102                                range: lsp::Range::new(
18103                                    lsp::Position::new(0, 11),
18104                                    lsp::Position::new(0, 12),
18105                                ),
18106                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18107                                ..Default::default()
18108                            },
18109                            lsp::Diagnostic {
18110                                range: lsp::Range::new(
18111                                    lsp::Position::new(0, 12),
18112                                    lsp::Position::new(0, 15),
18113                                ),
18114                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18115                                ..Default::default()
18116                            },
18117                            lsp::Diagnostic {
18118                                range: lsp::Range::new(
18119                                    lsp::Position::new(0, 25),
18120                                    lsp::Position::new(0, 28),
18121                                ),
18122                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18123                                ..Default::default()
18124                            },
18125                        ],
18126                    },
18127                    None,
18128                    DiagnosticSourceKind::Pushed,
18129                    &[],
18130                    cx,
18131                )
18132                .unwrap()
18133        });
18134    });
18135
18136    executor.run_until_parked();
18137
18138    cx.update_editor(|editor, window, cx| {
18139        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18140    });
18141
18142    cx.assert_editor_state(indoc! {"
18143        fn func(abc def: i32) -> ˇu32 {
18144        }
18145    "});
18146
18147    cx.update_editor(|editor, window, cx| {
18148        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18149    });
18150
18151    cx.assert_editor_state(indoc! {"
18152        fn func(abc ˇdef: i32) -> u32 {
18153        }
18154    "});
18155
18156    cx.update_editor(|editor, window, cx| {
18157        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18158    });
18159
18160    cx.assert_editor_state(indoc! {"
18161        fn func(abcˇ def: i32) -> u32 {
18162        }
18163    "});
18164
18165    cx.update_editor(|editor, window, cx| {
18166        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18167    });
18168
18169    cx.assert_editor_state(indoc! {"
18170        fn func(abc def: i32) -> ˇu32 {
18171        }
18172    "});
18173}
18174
18175#[gpui::test]
18176async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18177    init_test(cx, |_| {});
18178
18179    let mut cx = EditorTestContext::new(cx).await;
18180
18181    let diff_base = r#"
18182        use some::mod;
18183
18184        const A: u32 = 42;
18185
18186        fn main() {
18187            println!("hello");
18188
18189            println!("world");
18190        }
18191        "#
18192    .unindent();
18193
18194    // Edits are modified, removed, modified, added
18195    cx.set_state(
18196        &r#"
18197        use some::modified;
18198
18199        ˇ
18200        fn main() {
18201            println!("hello there");
18202
18203            println!("around the");
18204            println!("world");
18205        }
18206        "#
18207        .unindent(),
18208    );
18209
18210    cx.set_head_text(&diff_base);
18211    executor.run_until_parked();
18212
18213    cx.update_editor(|editor, window, cx| {
18214        //Wrap around the bottom of the buffer
18215        for _ in 0..3 {
18216            editor.go_to_next_hunk(&GoToHunk, window, cx);
18217        }
18218    });
18219
18220    cx.assert_editor_state(
18221        &r#"
18222        ˇuse some::modified;
18223
18224
18225        fn main() {
18226            println!("hello there");
18227
18228            println!("around the");
18229            println!("world");
18230        }
18231        "#
18232        .unindent(),
18233    );
18234
18235    cx.update_editor(|editor, window, cx| {
18236        //Wrap around the top of the buffer
18237        for _ in 0..2 {
18238            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18239        }
18240    });
18241
18242    cx.assert_editor_state(
18243        &r#"
18244        use some::modified;
18245
18246
18247        fn main() {
18248        ˇ    println!("hello there");
18249
18250            println!("around the");
18251            println!("world");
18252        }
18253        "#
18254        .unindent(),
18255    );
18256
18257    cx.update_editor(|editor, window, cx| {
18258        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18259    });
18260
18261    cx.assert_editor_state(
18262        &r#"
18263        use some::modified;
18264
18265        ˇ
18266        fn main() {
18267            println!("hello there");
18268
18269            println!("around the");
18270            println!("world");
18271        }
18272        "#
18273        .unindent(),
18274    );
18275
18276    cx.update_editor(|editor, window, cx| {
18277        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18278    });
18279
18280    cx.assert_editor_state(
18281        &r#"
18282        ˇuse some::modified;
18283
18284
18285        fn main() {
18286            println!("hello there");
18287
18288            println!("around the");
18289            println!("world");
18290        }
18291        "#
18292        .unindent(),
18293    );
18294
18295    cx.update_editor(|editor, window, cx| {
18296        for _ in 0..2 {
18297            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18298        }
18299    });
18300
18301    cx.assert_editor_state(
18302        &r#"
18303        use some::modified;
18304
18305
18306        fn main() {
18307        ˇ    println!("hello there");
18308
18309            println!("around the");
18310            println!("world");
18311        }
18312        "#
18313        .unindent(),
18314    );
18315
18316    cx.update_editor(|editor, window, cx| {
18317        editor.fold(&Fold, window, cx);
18318    });
18319
18320    cx.update_editor(|editor, window, cx| {
18321        editor.go_to_next_hunk(&GoToHunk, window, cx);
18322    });
18323
18324    cx.assert_editor_state(
18325        &r#"
18326        ˇuse some::modified;
18327
18328
18329        fn main() {
18330            println!("hello there");
18331
18332            println!("around the");
18333            println!("world");
18334        }
18335        "#
18336        .unindent(),
18337    );
18338}
18339
18340#[test]
18341fn test_split_words() {
18342    fn split(text: &str) -> Vec<&str> {
18343        split_words(text).collect()
18344    }
18345
18346    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18347    assert_eq!(split("hello_world"), &["hello_", "world"]);
18348    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18349    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18350    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18351    assert_eq!(split("helloworld"), &["helloworld"]);
18352
18353    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18354}
18355
18356#[test]
18357fn test_split_words_for_snippet_prefix() {
18358    fn split(text: &str) -> Vec<&str> {
18359        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18360    }
18361
18362    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18363    assert_eq!(split("hello_world"), &["hello_world"]);
18364    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18365    assert_eq!(split("Hello_World"), &["Hello_World"]);
18366    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18367    assert_eq!(split("helloworld"), &["helloworld"]);
18368    assert_eq!(
18369        split("this@is!@#$^many   . symbols"),
18370        &[
18371            "symbols",
18372            " symbols",
18373            ". symbols",
18374            " . symbols",
18375            "  . symbols",
18376            "   . symbols",
18377            "many   . symbols",
18378            "^many   . symbols",
18379            "$^many   . symbols",
18380            "#$^many   . symbols",
18381            "@#$^many   . symbols",
18382            "!@#$^many   . symbols",
18383            "is!@#$^many   . symbols",
18384            "@is!@#$^many   . symbols",
18385            "this@is!@#$^many   . symbols",
18386        ],
18387    );
18388    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18389}
18390
18391#[gpui::test]
18392async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18393    init_test(cx, |_| {});
18394
18395    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18396
18397    #[track_caller]
18398    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18399        let _state_context = cx.set_state(before);
18400        cx.run_until_parked();
18401        cx.update_editor(|editor, window, cx| {
18402            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18403        });
18404        cx.run_until_parked();
18405        cx.assert_editor_state(after);
18406    }
18407
18408    // Outside bracket jumps to outside of matching bracket
18409    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18410    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18411
18412    // Inside bracket jumps to inside of matching bracket
18413    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18414    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18415
18416    // When outside a bracket and inside, favor jumping to the inside bracket
18417    assert(
18418        "console.log('foo', [1, 2, 3]ˇ);",
18419        "console.log('foo', ˇ[1, 2, 3]);",
18420        &mut cx,
18421    );
18422    assert(
18423        "console.log(ˇ'foo', [1, 2, 3]);",
18424        "console.log('foo'ˇ, [1, 2, 3]);",
18425        &mut cx,
18426    );
18427
18428    // Bias forward if two options are equally likely
18429    assert(
18430        "let result = curried_fun()ˇ();",
18431        "let result = curried_fun()()ˇ;",
18432        &mut cx,
18433    );
18434
18435    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18436    assert(
18437        indoc! {"
18438            function test() {
18439                console.log('test')ˇ
18440            }"},
18441        indoc! {"
18442            function test() {
18443                console.logˇ('test')
18444            }"},
18445        &mut cx,
18446    );
18447}
18448
18449#[gpui::test]
18450async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18451    init_test(cx, |_| {});
18452    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18453    language_registry.add(markdown_lang());
18454    language_registry.add(rust_lang());
18455    let buffer = cx.new(|cx| {
18456        let mut buffer = language::Buffer::local(
18457            indoc! {"
18458            ```rs
18459            impl Worktree {
18460                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18461                }
18462            }
18463            ```
18464        "},
18465            cx,
18466        );
18467        buffer.set_language_registry(language_registry.clone());
18468        buffer.set_language(Some(markdown_lang()), cx);
18469        buffer
18470    });
18471    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18472    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18473    cx.executor().run_until_parked();
18474    _ = editor.update(cx, |editor, window, cx| {
18475        // Case 1: Test outer enclosing brackets
18476        select_ranges(
18477            editor,
18478            &indoc! {"
18479                ```rs
18480                impl Worktree {
18481                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18482                    }
1848318484                ```
18485            "},
18486            window,
18487            cx,
18488        );
18489        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18490        assert_text_with_selections(
18491            editor,
18492            &indoc! {"
18493                ```rs
18494                impl Worktree ˇ{
18495                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18496                    }
18497                }
18498                ```
18499            "},
18500            cx,
18501        );
18502        // Case 2: Test inner enclosing brackets
18503        select_ranges(
18504            editor,
18505            &indoc! {"
18506                ```rs
18507                impl Worktree {
18508                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1850918510                }
18511                ```
18512            "},
18513            window,
18514            cx,
18515        );
18516        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18517        assert_text_with_selections(
18518            editor,
18519            &indoc! {"
18520                ```rs
18521                impl Worktree {
18522                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18523                    }
18524                }
18525                ```
18526            "},
18527            cx,
18528        );
18529    });
18530}
18531
18532#[gpui::test]
18533async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18534    init_test(cx, |_| {});
18535
18536    let fs = FakeFs::new(cx.executor());
18537    fs.insert_tree(
18538        path!("/a"),
18539        json!({
18540            "main.rs": "fn main() { let a = 5; }",
18541            "other.rs": "// Test file",
18542        }),
18543    )
18544    .await;
18545    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18546
18547    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18548    language_registry.add(Arc::new(Language::new(
18549        LanguageConfig {
18550            name: "Rust".into(),
18551            matcher: LanguageMatcher {
18552                path_suffixes: vec!["rs".to_string()],
18553                ..Default::default()
18554            },
18555            brackets: BracketPairConfig {
18556                pairs: vec![BracketPair {
18557                    start: "{".to_string(),
18558                    end: "}".to_string(),
18559                    close: true,
18560                    surround: true,
18561                    newline: true,
18562                }],
18563                disabled_scopes_by_bracket_ix: Vec::new(),
18564            },
18565            ..Default::default()
18566        },
18567        Some(tree_sitter_rust::LANGUAGE.into()),
18568    )));
18569    let mut fake_servers = language_registry.register_fake_lsp(
18570        "Rust",
18571        FakeLspAdapter {
18572            capabilities: lsp::ServerCapabilities {
18573                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18574                    first_trigger_character: "{".to_string(),
18575                    more_trigger_character: None,
18576                }),
18577                ..Default::default()
18578            },
18579            ..Default::default()
18580        },
18581    );
18582
18583    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18584
18585    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18586
18587    let worktree_id = workspace
18588        .update(cx, |workspace, _, cx| {
18589            workspace.project().update(cx, |project, cx| {
18590                project.worktrees(cx).next().unwrap().read(cx).id()
18591            })
18592        })
18593        .unwrap();
18594
18595    let buffer = project
18596        .update(cx, |project, cx| {
18597            project.open_local_buffer(path!("/a/main.rs"), cx)
18598        })
18599        .await
18600        .unwrap();
18601    let editor_handle = workspace
18602        .update(cx, |workspace, window, cx| {
18603            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18604        })
18605        .unwrap()
18606        .await
18607        .unwrap()
18608        .downcast::<Editor>()
18609        .unwrap();
18610
18611    let fake_server = fake_servers.next().await.unwrap();
18612
18613    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18614        |params, _| async move {
18615            assert_eq!(
18616                params.text_document_position.text_document.uri,
18617                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18618            );
18619            assert_eq!(
18620                params.text_document_position.position,
18621                lsp::Position::new(0, 21),
18622            );
18623
18624            Ok(Some(vec![lsp::TextEdit {
18625                new_text: "]".to_string(),
18626                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18627            }]))
18628        },
18629    );
18630
18631    editor_handle.update_in(cx, |editor, window, cx| {
18632        window.focus(&editor.focus_handle(cx), cx);
18633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18634            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18635        });
18636        editor.handle_input("{", window, cx);
18637    });
18638
18639    cx.executor().run_until_parked();
18640
18641    buffer.update(cx, |buffer, _| {
18642        assert_eq!(
18643            buffer.text(),
18644            "fn main() { let a = {5}; }",
18645            "No extra braces from on type formatting should appear in the buffer"
18646        )
18647    });
18648}
18649
18650#[gpui::test(iterations = 20, seeds(31))]
18651async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18652    init_test(cx, |_| {});
18653
18654    let mut cx = EditorLspTestContext::new_rust(
18655        lsp::ServerCapabilities {
18656            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18657                first_trigger_character: ".".to_string(),
18658                more_trigger_character: None,
18659            }),
18660            ..Default::default()
18661        },
18662        cx,
18663    )
18664    .await;
18665
18666    cx.update_buffer(|buffer, _| {
18667        // This causes autoindent to be async.
18668        buffer.set_sync_parse_timeout(None)
18669    });
18670
18671    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18672    cx.simulate_keystroke("\n");
18673    cx.run_until_parked();
18674
18675    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18676    let mut request =
18677        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18678            let buffer_cloned = buffer_cloned.clone();
18679            async move {
18680                buffer_cloned.update(&mut cx, |buffer, _| {
18681                    assert_eq!(
18682                        buffer.text(),
18683                        "fn c() {\n    d()\n        .\n}\n",
18684                        "OnTypeFormatting should triggered after autoindent applied"
18685                    )
18686                });
18687
18688                Ok(Some(vec![]))
18689            }
18690        });
18691
18692    cx.simulate_keystroke(".");
18693    cx.run_until_parked();
18694
18695    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18696    assert!(request.next().await.is_some());
18697    request.close();
18698    assert!(request.next().await.is_none());
18699}
18700
18701#[gpui::test]
18702async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18703    init_test(cx, |_| {});
18704
18705    let fs = FakeFs::new(cx.executor());
18706    fs.insert_tree(
18707        path!("/a"),
18708        json!({
18709            "main.rs": "fn main() { let a = 5; }",
18710            "other.rs": "// Test file",
18711        }),
18712    )
18713    .await;
18714
18715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18716
18717    let server_restarts = Arc::new(AtomicUsize::new(0));
18718    let closure_restarts = Arc::clone(&server_restarts);
18719    let language_server_name = "test language server";
18720    let language_name: LanguageName = "Rust".into();
18721
18722    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18723    language_registry.add(Arc::new(Language::new(
18724        LanguageConfig {
18725            name: language_name.clone(),
18726            matcher: LanguageMatcher {
18727                path_suffixes: vec!["rs".to_string()],
18728                ..Default::default()
18729            },
18730            ..Default::default()
18731        },
18732        Some(tree_sitter_rust::LANGUAGE.into()),
18733    )));
18734    let mut fake_servers = language_registry.register_fake_lsp(
18735        "Rust",
18736        FakeLspAdapter {
18737            name: language_server_name,
18738            initialization_options: Some(json!({
18739                "testOptionValue": true
18740            })),
18741            initializer: Some(Box::new(move |fake_server| {
18742                let task_restarts = Arc::clone(&closure_restarts);
18743                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18744                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18745                    futures::future::ready(Ok(()))
18746                });
18747            })),
18748            ..Default::default()
18749        },
18750    );
18751
18752    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18753    let _buffer = project
18754        .update(cx, |project, cx| {
18755            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18756        })
18757        .await
18758        .unwrap();
18759    let _fake_server = fake_servers.next().await.unwrap();
18760    update_test_language_settings(cx, |language_settings| {
18761        language_settings.languages.0.insert(
18762            language_name.clone().0.to_string(),
18763            LanguageSettingsContent {
18764                tab_size: NonZeroU32::new(8),
18765                ..Default::default()
18766            },
18767        );
18768    });
18769    cx.executor().run_until_parked();
18770    assert_eq!(
18771        server_restarts.load(atomic::Ordering::Acquire),
18772        0,
18773        "Should not restart LSP server on an unrelated change"
18774    );
18775
18776    update_test_project_settings(cx, |project_settings| {
18777        project_settings.lsp.0.insert(
18778            "Some other server name".into(),
18779            LspSettings {
18780                binary: None,
18781                settings: None,
18782                initialization_options: Some(json!({
18783                    "some other init value": false
18784                })),
18785                enable_lsp_tasks: false,
18786                fetch: None,
18787            },
18788        );
18789    });
18790    cx.executor().run_until_parked();
18791    assert_eq!(
18792        server_restarts.load(atomic::Ordering::Acquire),
18793        0,
18794        "Should not restart LSP server on an unrelated LSP settings change"
18795    );
18796
18797    update_test_project_settings(cx, |project_settings| {
18798        project_settings.lsp.0.insert(
18799            language_server_name.into(),
18800            LspSettings {
18801                binary: None,
18802                settings: None,
18803                initialization_options: Some(json!({
18804                    "anotherInitValue": false
18805                })),
18806                enable_lsp_tasks: false,
18807                fetch: None,
18808            },
18809        );
18810    });
18811    cx.executor().run_until_parked();
18812    assert_eq!(
18813        server_restarts.load(atomic::Ordering::Acquire),
18814        1,
18815        "Should restart LSP server on a related LSP settings change"
18816    );
18817
18818    update_test_project_settings(cx, |project_settings| {
18819        project_settings.lsp.0.insert(
18820            language_server_name.into(),
18821            LspSettings {
18822                binary: None,
18823                settings: None,
18824                initialization_options: Some(json!({
18825                    "anotherInitValue": false
18826                })),
18827                enable_lsp_tasks: false,
18828                fetch: None,
18829            },
18830        );
18831    });
18832    cx.executor().run_until_parked();
18833    assert_eq!(
18834        server_restarts.load(atomic::Ordering::Acquire),
18835        1,
18836        "Should not restart LSP server on a related LSP settings change that is the same"
18837    );
18838
18839    update_test_project_settings(cx, |project_settings| {
18840        project_settings.lsp.0.insert(
18841            language_server_name.into(),
18842            LspSettings {
18843                binary: None,
18844                settings: None,
18845                initialization_options: None,
18846                enable_lsp_tasks: false,
18847                fetch: None,
18848            },
18849        );
18850    });
18851    cx.executor().run_until_parked();
18852    assert_eq!(
18853        server_restarts.load(atomic::Ordering::Acquire),
18854        2,
18855        "Should restart LSP server on another related LSP settings change"
18856    );
18857}
18858
18859#[gpui::test]
18860async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18861    init_test(cx, |_| {});
18862
18863    let mut cx = EditorLspTestContext::new_rust(
18864        lsp::ServerCapabilities {
18865            completion_provider: Some(lsp::CompletionOptions {
18866                trigger_characters: Some(vec![".".to_string()]),
18867                resolve_provider: Some(true),
18868                ..Default::default()
18869            }),
18870            ..Default::default()
18871        },
18872        cx,
18873    )
18874    .await;
18875
18876    cx.set_state("fn main() { let a = 2ˇ; }");
18877    cx.simulate_keystroke(".");
18878    let completion_item = lsp::CompletionItem {
18879        label: "some".into(),
18880        kind: Some(lsp::CompletionItemKind::SNIPPET),
18881        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18882        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18883            kind: lsp::MarkupKind::Markdown,
18884            value: "```rust\nSome(2)\n```".to_string(),
18885        })),
18886        deprecated: Some(false),
18887        sort_text: Some("fffffff2".to_string()),
18888        filter_text: Some("some".to_string()),
18889        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18890        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18891            range: lsp::Range {
18892                start: lsp::Position {
18893                    line: 0,
18894                    character: 22,
18895                },
18896                end: lsp::Position {
18897                    line: 0,
18898                    character: 22,
18899                },
18900            },
18901            new_text: "Some(2)".to_string(),
18902        })),
18903        additional_text_edits: Some(vec![lsp::TextEdit {
18904            range: lsp::Range {
18905                start: lsp::Position {
18906                    line: 0,
18907                    character: 20,
18908                },
18909                end: lsp::Position {
18910                    line: 0,
18911                    character: 22,
18912                },
18913            },
18914            new_text: "".to_string(),
18915        }]),
18916        ..Default::default()
18917    };
18918
18919    let closure_completion_item = completion_item.clone();
18920    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18921        let task_completion_item = closure_completion_item.clone();
18922        async move {
18923            Ok(Some(lsp::CompletionResponse::Array(vec![
18924                task_completion_item,
18925            ])))
18926        }
18927    });
18928
18929    request.next().await;
18930
18931    cx.condition(|editor, _| editor.context_menu_visible())
18932        .await;
18933    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18934        editor
18935            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18936            .unwrap()
18937    });
18938    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18939
18940    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18941        let task_completion_item = completion_item.clone();
18942        async move { Ok(task_completion_item) }
18943    })
18944    .next()
18945    .await
18946    .unwrap();
18947    apply_additional_edits.await.unwrap();
18948    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18949}
18950
18951#[gpui::test]
18952async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18953    init_test(cx, |_| {});
18954
18955    let mut cx = EditorLspTestContext::new_rust(
18956        lsp::ServerCapabilities {
18957            completion_provider: Some(lsp::CompletionOptions {
18958                trigger_characters: Some(vec![".".to_string()]),
18959                resolve_provider: Some(true),
18960                ..Default::default()
18961            }),
18962            ..Default::default()
18963        },
18964        cx,
18965    )
18966    .await;
18967
18968    cx.set_state("fn main() { let a = 2ˇ; }");
18969    cx.simulate_keystroke(".");
18970
18971    let item1 = lsp::CompletionItem {
18972        label: "method id()".to_string(),
18973        filter_text: Some("id".to_string()),
18974        detail: None,
18975        documentation: None,
18976        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18977            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18978            new_text: ".id".to_string(),
18979        })),
18980        ..lsp::CompletionItem::default()
18981    };
18982
18983    let item2 = lsp::CompletionItem {
18984        label: "other".to_string(),
18985        filter_text: Some("other".to_string()),
18986        detail: None,
18987        documentation: None,
18988        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18989            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18990            new_text: ".other".to_string(),
18991        })),
18992        ..lsp::CompletionItem::default()
18993    };
18994
18995    let item1 = item1.clone();
18996    cx.set_request_handler::<lsp::request::Completion, _, _>({
18997        let item1 = item1.clone();
18998        move |_, _, _| {
18999            let item1 = item1.clone();
19000            let item2 = item2.clone();
19001            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
19002        }
19003    })
19004    .next()
19005    .await;
19006
19007    cx.condition(|editor, _| editor.context_menu_visible())
19008        .await;
19009    cx.update_editor(|editor, _, _| {
19010        let context_menu = editor.context_menu.borrow_mut();
19011        let context_menu = context_menu
19012            .as_ref()
19013            .expect("Should have the context menu deployed");
19014        match context_menu {
19015            CodeContextMenu::Completions(completions_menu) => {
19016                let completions = completions_menu.completions.borrow_mut();
19017                assert_eq!(
19018                    completions
19019                        .iter()
19020                        .map(|completion| &completion.label.text)
19021                        .collect::<Vec<_>>(),
19022                    vec!["method id()", "other"]
19023                )
19024            }
19025            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19026        }
19027    });
19028
19029    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
19030        let item1 = item1.clone();
19031        move |_, item_to_resolve, _| {
19032            let item1 = item1.clone();
19033            async move {
19034                if item1 == item_to_resolve {
19035                    Ok(lsp::CompletionItem {
19036                        label: "method id()".to_string(),
19037                        filter_text: Some("id".to_string()),
19038                        detail: Some("Now resolved!".to_string()),
19039                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
19040                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19041                            range: lsp::Range::new(
19042                                lsp::Position::new(0, 22),
19043                                lsp::Position::new(0, 22),
19044                            ),
19045                            new_text: ".id".to_string(),
19046                        })),
19047                        ..lsp::CompletionItem::default()
19048                    })
19049                } else {
19050                    Ok(item_to_resolve)
19051                }
19052            }
19053        }
19054    })
19055    .next()
19056    .await
19057    .unwrap();
19058    cx.run_until_parked();
19059
19060    cx.update_editor(|editor, window, cx| {
19061        editor.context_menu_next(&Default::default(), window, cx);
19062    });
19063    cx.run_until_parked();
19064
19065    cx.update_editor(|editor, _, _| {
19066        let context_menu = editor.context_menu.borrow_mut();
19067        let context_menu = context_menu
19068            .as_ref()
19069            .expect("Should have the context menu deployed");
19070        match context_menu {
19071            CodeContextMenu::Completions(completions_menu) => {
19072                let completions = completions_menu.completions.borrow_mut();
19073                assert_eq!(
19074                    completions
19075                        .iter()
19076                        .map(|completion| &completion.label.text)
19077                        .collect::<Vec<_>>(),
19078                    vec!["method id() Now resolved!", "other"],
19079                    "Should update first completion label, but not second as the filter text did not match."
19080                );
19081            }
19082            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19083        }
19084    });
19085}
19086
19087#[gpui::test]
19088async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
19089    init_test(cx, |_| {});
19090    let mut cx = EditorLspTestContext::new_rust(
19091        lsp::ServerCapabilities {
19092            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
19093            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
19094            completion_provider: Some(lsp::CompletionOptions {
19095                resolve_provider: Some(true),
19096                ..Default::default()
19097            }),
19098            ..Default::default()
19099        },
19100        cx,
19101    )
19102    .await;
19103    cx.set_state(indoc! {"
19104        struct TestStruct {
19105            field: i32
19106        }
19107
19108        fn mainˇ() {
19109            let unused_var = 42;
19110            let test_struct = TestStruct { field: 42 };
19111        }
19112    "});
19113    let symbol_range = cx.lsp_range(indoc! {"
19114        struct TestStruct {
19115            field: i32
19116        }
19117
19118        «fn main»() {
19119            let unused_var = 42;
19120            let test_struct = TestStruct { field: 42 };
19121        }
19122    "});
19123    let mut hover_requests =
19124        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
19125            Ok(Some(lsp::Hover {
19126                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
19127                    kind: lsp::MarkupKind::Markdown,
19128                    value: "Function documentation".to_string(),
19129                }),
19130                range: Some(symbol_range),
19131            }))
19132        });
19133
19134    // Case 1: Test that code action menu hide hover popover
19135    cx.dispatch_action(Hover);
19136    hover_requests.next().await;
19137    cx.condition(|editor, _| editor.hover_state.visible()).await;
19138    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
19139        move |_, _, _| async move {
19140            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
19141                lsp::CodeAction {
19142                    title: "Remove unused variable".to_string(),
19143                    kind: Some(CodeActionKind::QUICKFIX),
19144                    edit: Some(lsp::WorkspaceEdit {
19145                        changes: Some(
19146                            [(
19147                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
19148                                vec![lsp::TextEdit {
19149                                    range: lsp::Range::new(
19150                                        lsp::Position::new(5, 4),
19151                                        lsp::Position::new(5, 27),
19152                                    ),
19153                                    new_text: "".to_string(),
19154                                }],
19155                            )]
19156                            .into_iter()
19157                            .collect(),
19158                        ),
19159                        ..Default::default()
19160                    }),
19161                    ..Default::default()
19162                },
19163            )]))
19164        },
19165    );
19166    cx.update_editor(|editor, window, cx| {
19167        editor.toggle_code_actions(
19168            &ToggleCodeActions {
19169                deployed_from: None,
19170                quick_launch: false,
19171            },
19172            window,
19173            cx,
19174        );
19175    });
19176    code_action_requests.next().await;
19177    cx.run_until_parked();
19178    cx.condition(|editor, _| editor.context_menu_visible())
19179        .await;
19180    cx.update_editor(|editor, _, _| {
19181        assert!(
19182            !editor.hover_state.visible(),
19183            "Hover popover should be hidden when code action menu is shown"
19184        );
19185        // Hide code actions
19186        editor.context_menu.take();
19187    });
19188
19189    // Case 2: Test that code completions hide hover popover
19190    cx.dispatch_action(Hover);
19191    hover_requests.next().await;
19192    cx.condition(|editor, _| editor.hover_state.visible()).await;
19193    let counter = Arc::new(AtomicUsize::new(0));
19194    let mut completion_requests =
19195        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19196            let counter = counter.clone();
19197            async move {
19198                counter.fetch_add(1, atomic::Ordering::Release);
19199                Ok(Some(lsp::CompletionResponse::Array(vec![
19200                    lsp::CompletionItem {
19201                        label: "main".into(),
19202                        kind: Some(lsp::CompletionItemKind::FUNCTION),
19203                        detail: Some("() -> ()".to_string()),
19204                        ..Default::default()
19205                    },
19206                    lsp::CompletionItem {
19207                        label: "TestStruct".into(),
19208                        kind: Some(lsp::CompletionItemKind::STRUCT),
19209                        detail: Some("struct TestStruct".to_string()),
19210                        ..Default::default()
19211                    },
19212                ])))
19213            }
19214        });
19215    cx.update_editor(|editor, window, cx| {
19216        editor.show_completions(&ShowCompletions, window, cx);
19217    });
19218    completion_requests.next().await;
19219    cx.condition(|editor, _| editor.context_menu_visible())
19220        .await;
19221    cx.update_editor(|editor, _, _| {
19222        assert!(
19223            !editor.hover_state.visible(),
19224            "Hover popover should be hidden when completion menu is shown"
19225        );
19226    });
19227}
19228
19229#[gpui::test]
19230async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
19231    init_test(cx, |_| {});
19232
19233    let mut cx = EditorLspTestContext::new_rust(
19234        lsp::ServerCapabilities {
19235            completion_provider: Some(lsp::CompletionOptions {
19236                trigger_characters: Some(vec![".".to_string()]),
19237                resolve_provider: Some(true),
19238                ..Default::default()
19239            }),
19240            ..Default::default()
19241        },
19242        cx,
19243    )
19244    .await;
19245
19246    cx.set_state("fn main() { let a = 2ˇ; }");
19247    cx.simulate_keystroke(".");
19248
19249    let unresolved_item_1 = lsp::CompletionItem {
19250        label: "id".to_string(),
19251        filter_text: Some("id".to_string()),
19252        detail: None,
19253        documentation: None,
19254        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19255            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19256            new_text: ".id".to_string(),
19257        })),
19258        ..lsp::CompletionItem::default()
19259    };
19260    let resolved_item_1 = lsp::CompletionItem {
19261        additional_text_edits: Some(vec![lsp::TextEdit {
19262            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19263            new_text: "!!".to_string(),
19264        }]),
19265        ..unresolved_item_1.clone()
19266    };
19267    let unresolved_item_2 = lsp::CompletionItem {
19268        label: "other".to_string(),
19269        filter_text: Some("other".to_string()),
19270        detail: None,
19271        documentation: None,
19272        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19273            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19274            new_text: ".other".to_string(),
19275        })),
19276        ..lsp::CompletionItem::default()
19277    };
19278    let resolved_item_2 = lsp::CompletionItem {
19279        additional_text_edits: Some(vec![lsp::TextEdit {
19280            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19281            new_text: "??".to_string(),
19282        }]),
19283        ..unresolved_item_2.clone()
19284    };
19285
19286    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19287    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19288    cx.lsp
19289        .server
19290        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19291            let unresolved_item_1 = unresolved_item_1.clone();
19292            let resolved_item_1 = resolved_item_1.clone();
19293            let unresolved_item_2 = unresolved_item_2.clone();
19294            let resolved_item_2 = resolved_item_2.clone();
19295            let resolve_requests_1 = resolve_requests_1.clone();
19296            let resolve_requests_2 = resolve_requests_2.clone();
19297            move |unresolved_request, _| {
19298                let unresolved_item_1 = unresolved_item_1.clone();
19299                let resolved_item_1 = resolved_item_1.clone();
19300                let unresolved_item_2 = unresolved_item_2.clone();
19301                let resolved_item_2 = resolved_item_2.clone();
19302                let resolve_requests_1 = resolve_requests_1.clone();
19303                let resolve_requests_2 = resolve_requests_2.clone();
19304                async move {
19305                    if unresolved_request == unresolved_item_1 {
19306                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19307                        Ok(resolved_item_1.clone())
19308                    } else if unresolved_request == unresolved_item_2 {
19309                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19310                        Ok(resolved_item_2.clone())
19311                    } else {
19312                        panic!("Unexpected completion item {unresolved_request:?}")
19313                    }
19314                }
19315            }
19316        })
19317        .detach();
19318
19319    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19320        let unresolved_item_1 = unresolved_item_1.clone();
19321        let unresolved_item_2 = unresolved_item_2.clone();
19322        async move {
19323            Ok(Some(lsp::CompletionResponse::Array(vec![
19324                unresolved_item_1,
19325                unresolved_item_2,
19326            ])))
19327        }
19328    })
19329    .next()
19330    .await;
19331
19332    cx.condition(|editor, _| editor.context_menu_visible())
19333        .await;
19334    cx.update_editor(|editor, _, _| {
19335        let context_menu = editor.context_menu.borrow_mut();
19336        let context_menu = context_menu
19337            .as_ref()
19338            .expect("Should have the context menu deployed");
19339        match context_menu {
19340            CodeContextMenu::Completions(completions_menu) => {
19341                let completions = completions_menu.completions.borrow_mut();
19342                assert_eq!(
19343                    completions
19344                        .iter()
19345                        .map(|completion| &completion.label.text)
19346                        .collect::<Vec<_>>(),
19347                    vec!["id", "other"]
19348                )
19349            }
19350            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19351        }
19352    });
19353    cx.run_until_parked();
19354
19355    cx.update_editor(|editor, window, cx| {
19356        editor.context_menu_next(&ContextMenuNext, window, cx);
19357    });
19358    cx.run_until_parked();
19359    cx.update_editor(|editor, window, cx| {
19360        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19361    });
19362    cx.run_until_parked();
19363    cx.update_editor(|editor, window, cx| {
19364        editor.context_menu_next(&ContextMenuNext, window, cx);
19365    });
19366    cx.run_until_parked();
19367    cx.update_editor(|editor, window, cx| {
19368        editor
19369            .compose_completion(&ComposeCompletion::default(), window, cx)
19370            .expect("No task returned")
19371    })
19372    .await
19373    .expect("Completion failed");
19374    cx.run_until_parked();
19375
19376    cx.update_editor(|editor, _, cx| {
19377        assert_eq!(
19378            resolve_requests_1.load(atomic::Ordering::Acquire),
19379            1,
19380            "Should always resolve once despite multiple selections"
19381        );
19382        assert_eq!(
19383            resolve_requests_2.load(atomic::Ordering::Acquire),
19384            1,
19385            "Should always resolve once after multiple selections and applying the completion"
19386        );
19387        assert_eq!(
19388            editor.text(cx),
19389            "fn main() { let a = ??.other; }",
19390            "Should use resolved data when applying the completion"
19391        );
19392    });
19393}
19394
19395#[gpui::test]
19396async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19397    init_test(cx, |_| {});
19398
19399    let item_0 = lsp::CompletionItem {
19400        label: "abs".into(),
19401        insert_text: Some("abs".into()),
19402        data: Some(json!({ "very": "special"})),
19403        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19404        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19405            lsp::InsertReplaceEdit {
19406                new_text: "abs".to_string(),
19407                insert: lsp::Range::default(),
19408                replace: lsp::Range::default(),
19409            },
19410        )),
19411        ..lsp::CompletionItem::default()
19412    };
19413    let items = iter::once(item_0.clone())
19414        .chain((11..51).map(|i| lsp::CompletionItem {
19415            label: format!("item_{}", i),
19416            insert_text: Some(format!("item_{}", i)),
19417            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19418            ..lsp::CompletionItem::default()
19419        }))
19420        .collect::<Vec<_>>();
19421
19422    let default_commit_characters = vec!["?".to_string()];
19423    let default_data = json!({ "default": "data"});
19424    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19425    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19426    let default_edit_range = lsp::Range {
19427        start: lsp::Position {
19428            line: 0,
19429            character: 5,
19430        },
19431        end: lsp::Position {
19432            line: 0,
19433            character: 5,
19434        },
19435    };
19436
19437    let mut cx = EditorLspTestContext::new_rust(
19438        lsp::ServerCapabilities {
19439            completion_provider: Some(lsp::CompletionOptions {
19440                trigger_characters: Some(vec![".".to_string()]),
19441                resolve_provider: Some(true),
19442                ..Default::default()
19443            }),
19444            ..Default::default()
19445        },
19446        cx,
19447    )
19448    .await;
19449
19450    cx.set_state("fn main() { let a = 2ˇ; }");
19451    cx.simulate_keystroke(".");
19452
19453    let completion_data = default_data.clone();
19454    let completion_characters = default_commit_characters.clone();
19455    let completion_items = items.clone();
19456    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19457        let default_data = completion_data.clone();
19458        let default_commit_characters = completion_characters.clone();
19459        let items = completion_items.clone();
19460        async move {
19461            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19462                items,
19463                item_defaults: Some(lsp::CompletionListItemDefaults {
19464                    data: Some(default_data.clone()),
19465                    commit_characters: Some(default_commit_characters.clone()),
19466                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19467                        default_edit_range,
19468                    )),
19469                    insert_text_format: Some(default_insert_text_format),
19470                    insert_text_mode: Some(default_insert_text_mode),
19471                }),
19472                ..lsp::CompletionList::default()
19473            })))
19474        }
19475    })
19476    .next()
19477    .await;
19478
19479    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19480    cx.lsp
19481        .server
19482        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19483            let closure_resolved_items = resolved_items.clone();
19484            move |item_to_resolve, _| {
19485                let closure_resolved_items = closure_resolved_items.clone();
19486                async move {
19487                    closure_resolved_items.lock().push(item_to_resolve.clone());
19488                    Ok(item_to_resolve)
19489                }
19490            }
19491        })
19492        .detach();
19493
19494    cx.condition(|editor, _| editor.context_menu_visible())
19495        .await;
19496    cx.run_until_parked();
19497    cx.update_editor(|editor, _, _| {
19498        let menu = editor.context_menu.borrow_mut();
19499        match menu.as_ref().expect("should have the completions menu") {
19500            CodeContextMenu::Completions(completions_menu) => {
19501                assert_eq!(
19502                    completions_menu
19503                        .entries
19504                        .borrow()
19505                        .iter()
19506                        .map(|mat| mat.string.clone())
19507                        .collect::<Vec<String>>(),
19508                    items
19509                        .iter()
19510                        .map(|completion| completion.label.clone())
19511                        .collect::<Vec<String>>()
19512                );
19513            }
19514            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19515        }
19516    });
19517    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19518    // with 4 from the end.
19519    assert_eq!(
19520        *resolved_items.lock(),
19521        [&items[0..16], &items[items.len() - 4..items.len()]]
19522            .concat()
19523            .iter()
19524            .cloned()
19525            .map(|mut item| {
19526                if item.data.is_none() {
19527                    item.data = Some(default_data.clone());
19528                }
19529                item
19530            })
19531            .collect::<Vec<lsp::CompletionItem>>(),
19532        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19533    );
19534    resolved_items.lock().clear();
19535
19536    cx.update_editor(|editor, window, cx| {
19537        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19538    });
19539    cx.run_until_parked();
19540    // Completions that have already been resolved are skipped.
19541    assert_eq!(
19542        *resolved_items.lock(),
19543        items[items.len() - 17..items.len() - 4]
19544            .iter()
19545            .cloned()
19546            .map(|mut item| {
19547                if item.data.is_none() {
19548                    item.data = Some(default_data.clone());
19549                }
19550                item
19551            })
19552            .collect::<Vec<lsp::CompletionItem>>()
19553    );
19554    resolved_items.lock().clear();
19555}
19556
19557#[gpui::test]
19558async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19559    init_test(cx, |_| {});
19560
19561    let mut cx = EditorLspTestContext::new(
19562        Language::new(
19563            LanguageConfig {
19564                matcher: LanguageMatcher {
19565                    path_suffixes: vec!["jsx".into()],
19566                    ..Default::default()
19567                },
19568                overrides: [(
19569                    "element".into(),
19570                    LanguageConfigOverride {
19571                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19572                        ..Default::default()
19573                    },
19574                )]
19575                .into_iter()
19576                .collect(),
19577                ..Default::default()
19578            },
19579            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19580        )
19581        .with_override_query("(jsx_self_closing_element) @element")
19582        .unwrap(),
19583        lsp::ServerCapabilities {
19584            completion_provider: Some(lsp::CompletionOptions {
19585                trigger_characters: Some(vec![":".to_string()]),
19586                ..Default::default()
19587            }),
19588            ..Default::default()
19589        },
19590        cx,
19591    )
19592    .await;
19593
19594    cx.lsp
19595        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19596            Ok(Some(lsp::CompletionResponse::Array(vec![
19597                lsp::CompletionItem {
19598                    label: "bg-blue".into(),
19599                    ..Default::default()
19600                },
19601                lsp::CompletionItem {
19602                    label: "bg-red".into(),
19603                    ..Default::default()
19604                },
19605                lsp::CompletionItem {
19606                    label: "bg-yellow".into(),
19607                    ..Default::default()
19608                },
19609            ])))
19610        });
19611
19612    cx.set_state(r#"<p class="bgˇ" />"#);
19613
19614    // Trigger completion when typing a dash, because the dash is an extra
19615    // word character in the 'element' scope, which contains the cursor.
19616    cx.simulate_keystroke("-");
19617    cx.executor().run_until_parked();
19618    cx.update_editor(|editor, _, _| {
19619        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19620        {
19621            assert_eq!(
19622                completion_menu_entries(menu),
19623                &["bg-blue", "bg-red", "bg-yellow"]
19624            );
19625        } else {
19626            panic!("expected completion menu to be open");
19627        }
19628    });
19629
19630    cx.simulate_keystroke("l");
19631    cx.executor().run_until_parked();
19632    cx.update_editor(|editor, _, _| {
19633        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19634        {
19635            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19636        } else {
19637            panic!("expected completion menu to be open");
19638        }
19639    });
19640
19641    // When filtering completions, consider the character after the '-' to
19642    // be the start of a subword.
19643    cx.set_state(r#"<p class="yelˇ" />"#);
19644    cx.simulate_keystroke("l");
19645    cx.executor().run_until_parked();
19646    cx.update_editor(|editor, _, _| {
19647        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19648        {
19649            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19650        } else {
19651            panic!("expected completion menu to be open");
19652        }
19653    });
19654}
19655
19656fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19657    let entries = menu.entries.borrow();
19658    entries.iter().map(|mat| mat.string.clone()).collect()
19659}
19660
19661#[gpui::test]
19662async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19663    init_test(cx, |settings| {
19664        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19665    });
19666
19667    let fs = FakeFs::new(cx.executor());
19668    fs.insert_file(path!("/file.ts"), Default::default()).await;
19669
19670    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19671    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19672
19673    language_registry.add(Arc::new(Language::new(
19674        LanguageConfig {
19675            name: "TypeScript".into(),
19676            matcher: LanguageMatcher {
19677                path_suffixes: vec!["ts".to_string()],
19678                ..Default::default()
19679            },
19680            ..Default::default()
19681        },
19682        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19683    )));
19684    update_test_language_settings(cx, |settings| {
19685        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19686    });
19687
19688    let test_plugin = "test_plugin";
19689    let _ = language_registry.register_fake_lsp(
19690        "TypeScript",
19691        FakeLspAdapter {
19692            prettier_plugins: vec![test_plugin],
19693            ..Default::default()
19694        },
19695    );
19696
19697    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19698    let buffer = project
19699        .update(cx, |project, cx| {
19700            project.open_local_buffer(path!("/file.ts"), cx)
19701        })
19702        .await
19703        .unwrap();
19704
19705    let buffer_text = "one\ntwo\nthree\n";
19706    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19707    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19708    editor.update_in(cx, |editor, window, cx| {
19709        editor.set_text(buffer_text, window, cx)
19710    });
19711
19712    editor
19713        .update_in(cx, |editor, window, cx| {
19714            editor.perform_format(
19715                project.clone(),
19716                FormatTrigger::Manual,
19717                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19718                window,
19719                cx,
19720            )
19721        })
19722        .unwrap()
19723        .await;
19724    assert_eq!(
19725        editor.update(cx, |editor, cx| editor.text(cx)),
19726        buffer_text.to_string() + prettier_format_suffix,
19727        "Test prettier formatting was not applied to the original buffer text",
19728    );
19729
19730    update_test_language_settings(cx, |settings| {
19731        settings.defaults.formatter = Some(FormatterList::default())
19732    });
19733    let format = editor.update_in(cx, |editor, window, cx| {
19734        editor.perform_format(
19735            project.clone(),
19736            FormatTrigger::Manual,
19737            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19738            window,
19739            cx,
19740        )
19741    });
19742    format.await.unwrap();
19743    assert_eq!(
19744        editor.update(cx, |editor, cx| editor.text(cx)),
19745        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19746        "Autoformatting (via test prettier) was not applied to the original buffer text",
19747    );
19748}
19749
19750#[gpui::test]
19751async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19752    init_test(cx, |settings| {
19753        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19754    });
19755
19756    let fs = FakeFs::new(cx.executor());
19757    fs.insert_file(path!("/file.settings"), Default::default())
19758        .await;
19759
19760    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19761    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19762
19763    let ts_lang = Arc::new(Language::new(
19764        LanguageConfig {
19765            name: "TypeScript".into(),
19766            matcher: LanguageMatcher {
19767                path_suffixes: vec!["ts".to_string()],
19768                ..LanguageMatcher::default()
19769            },
19770            prettier_parser_name: Some("typescript".to_string()),
19771            ..LanguageConfig::default()
19772        },
19773        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19774    ));
19775
19776    language_registry.add(ts_lang.clone());
19777
19778    update_test_language_settings(cx, |settings| {
19779        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19780    });
19781
19782    let test_plugin = "test_plugin";
19783    let _ = language_registry.register_fake_lsp(
19784        "TypeScript",
19785        FakeLspAdapter {
19786            prettier_plugins: vec![test_plugin],
19787            ..Default::default()
19788        },
19789    );
19790
19791    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19792    let buffer = project
19793        .update(cx, |project, cx| {
19794            project.open_local_buffer(path!("/file.settings"), cx)
19795        })
19796        .await
19797        .unwrap();
19798
19799    project.update(cx, |project, cx| {
19800        project.set_language_for_buffer(&buffer, ts_lang, cx)
19801    });
19802
19803    let buffer_text = "one\ntwo\nthree\n";
19804    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19805    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19806    editor.update_in(cx, |editor, window, cx| {
19807        editor.set_text(buffer_text, window, cx)
19808    });
19809
19810    editor
19811        .update_in(cx, |editor, window, cx| {
19812            editor.perform_format(
19813                project.clone(),
19814                FormatTrigger::Manual,
19815                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19816                window,
19817                cx,
19818            )
19819        })
19820        .unwrap()
19821        .await;
19822    assert_eq!(
19823        editor.update(cx, |editor, cx| editor.text(cx)),
19824        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19825        "Test prettier formatting was not applied to the original buffer text",
19826    );
19827
19828    update_test_language_settings(cx, |settings| {
19829        settings.defaults.formatter = Some(FormatterList::default())
19830    });
19831    let format = editor.update_in(cx, |editor, window, cx| {
19832        editor.perform_format(
19833            project.clone(),
19834            FormatTrigger::Manual,
19835            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19836            window,
19837            cx,
19838        )
19839    });
19840    format.await.unwrap();
19841
19842    assert_eq!(
19843        editor.update(cx, |editor, cx| editor.text(cx)),
19844        buffer_text.to_string()
19845            + prettier_format_suffix
19846            + "\ntypescript\n"
19847            + prettier_format_suffix
19848            + "\ntypescript",
19849        "Autoformatting (via test prettier) was not applied to the original buffer text",
19850    );
19851}
19852
19853#[gpui::test]
19854async fn test_addition_reverts(cx: &mut TestAppContext) {
19855    init_test(cx, |_| {});
19856    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19857    let base_text = indoc! {r#"
19858        struct Row;
19859        struct Row1;
19860        struct Row2;
19861
19862        struct Row4;
19863        struct Row5;
19864        struct Row6;
19865
19866        struct Row8;
19867        struct Row9;
19868        struct Row10;"#};
19869
19870    // When addition hunks are not adjacent to carets, no hunk revert is performed
19871    assert_hunk_revert(
19872        indoc! {r#"struct Row;
19873                   struct Row1;
19874                   struct Row1.1;
19875                   struct Row1.2;
19876                   struct Row2;ˇ
19877
19878                   struct Row4;
19879                   struct Row5;
19880                   struct Row6;
19881
19882                   struct Row8;
19883                   ˇstruct Row9;
19884                   struct Row9.1;
19885                   struct Row9.2;
19886                   struct Row9.3;
19887                   struct Row10;"#},
19888        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19889        indoc! {r#"struct Row;
19890                   struct Row1;
19891                   struct Row1.1;
19892                   struct Row1.2;
19893                   struct Row2;ˇ
19894
19895                   struct Row4;
19896                   struct Row5;
19897                   struct Row6;
19898
19899                   struct Row8;
19900                   ˇstruct Row9;
19901                   struct Row9.1;
19902                   struct Row9.2;
19903                   struct Row9.3;
19904                   struct Row10;"#},
19905        base_text,
19906        &mut cx,
19907    );
19908    // Same for selections
19909    assert_hunk_revert(
19910        indoc! {r#"struct Row;
19911                   struct Row1;
19912                   struct Row2;
19913                   struct Row2.1;
19914                   struct Row2.2;
19915                   «ˇ
19916                   struct Row4;
19917                   struct» Row5;
19918                   «struct Row6;
19919                   ˇ»
19920                   struct Row9.1;
19921                   struct Row9.2;
19922                   struct Row9.3;
19923                   struct Row8;
19924                   struct Row9;
19925                   struct Row10;"#},
19926        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19927        indoc! {r#"struct Row;
19928                   struct Row1;
19929                   struct Row2;
19930                   struct Row2.1;
19931                   struct Row2.2;
19932                   «ˇ
19933                   struct Row4;
19934                   struct» Row5;
19935                   «struct Row6;
19936                   ˇ»
19937                   struct Row9.1;
19938                   struct Row9.2;
19939                   struct Row9.3;
19940                   struct Row8;
19941                   struct Row9;
19942                   struct Row10;"#},
19943        base_text,
19944        &mut cx,
19945    );
19946
19947    // When carets and selections intersect the addition hunks, those are reverted.
19948    // Adjacent carets got merged.
19949    assert_hunk_revert(
19950        indoc! {r#"struct Row;
19951                   ˇ// something on the top
19952                   struct Row1;
19953                   struct Row2;
19954                   struct Roˇw3.1;
19955                   struct Row2.2;
19956                   struct Row2.3;ˇ
19957
19958                   struct Row4;
19959                   struct ˇRow5.1;
19960                   struct Row5.2;
19961                   struct «Rowˇ»5.3;
19962                   struct Row5;
19963                   struct Row6;
19964                   ˇ
19965                   struct Row9.1;
19966                   struct «Rowˇ»9.2;
19967                   struct «ˇRow»9.3;
19968                   struct Row8;
19969                   struct Row9;
19970                   «ˇ// something on bottom»
19971                   struct Row10;"#},
19972        vec![
19973            DiffHunkStatusKind::Added,
19974            DiffHunkStatusKind::Added,
19975            DiffHunkStatusKind::Added,
19976            DiffHunkStatusKind::Added,
19977            DiffHunkStatusKind::Added,
19978        ],
19979        indoc! {r#"struct Row;
19980                   ˇstruct Row1;
19981                   struct Row2;
19982                   ˇ
19983                   struct Row4;
19984                   ˇstruct Row5;
19985                   struct Row6;
19986                   ˇ
19987                   ˇstruct Row8;
19988                   struct Row9;
19989                   ˇstruct Row10;"#},
19990        base_text,
19991        &mut cx,
19992    );
19993}
19994
19995#[gpui::test]
19996async fn test_modification_reverts(cx: &mut TestAppContext) {
19997    init_test(cx, |_| {});
19998    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19999    let base_text = indoc! {r#"
20000        struct Row;
20001        struct Row1;
20002        struct Row2;
20003
20004        struct Row4;
20005        struct Row5;
20006        struct Row6;
20007
20008        struct Row8;
20009        struct Row9;
20010        struct Row10;"#};
20011
20012    // Modification hunks behave the same as the addition ones.
20013    assert_hunk_revert(
20014        indoc! {r#"struct Row;
20015                   struct Row1;
20016                   struct Row33;
20017                   ˇ
20018                   struct Row4;
20019                   struct Row5;
20020                   struct Row6;
20021                   ˇ
20022                   struct Row99;
20023                   struct Row9;
20024                   struct Row10;"#},
20025        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20026        indoc! {r#"struct Row;
20027                   struct Row1;
20028                   struct Row33;
20029                   ˇ
20030                   struct Row4;
20031                   struct Row5;
20032                   struct Row6;
20033                   ˇ
20034                   struct Row99;
20035                   struct Row9;
20036                   struct Row10;"#},
20037        base_text,
20038        &mut cx,
20039    );
20040    assert_hunk_revert(
20041        indoc! {r#"struct Row;
20042                   struct Row1;
20043                   struct Row33;
20044                   «ˇ
20045                   struct Row4;
20046                   struct» Row5;
20047                   «struct Row6;
20048                   ˇ»
20049                   struct Row99;
20050                   struct Row9;
20051                   struct Row10;"#},
20052        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20053        indoc! {r#"struct Row;
20054                   struct Row1;
20055                   struct Row33;
20056                   «ˇ
20057                   struct Row4;
20058                   struct» Row5;
20059                   «struct Row6;
20060                   ˇ»
20061                   struct Row99;
20062                   struct Row9;
20063                   struct Row10;"#},
20064        base_text,
20065        &mut cx,
20066    );
20067
20068    assert_hunk_revert(
20069        indoc! {r#"ˇstruct Row1.1;
20070                   struct Row1;
20071                   «ˇstr»uct Row22;
20072
20073                   struct ˇRow44;
20074                   struct Row5;
20075                   struct «Rˇ»ow66;ˇ
20076
20077                   «struˇ»ct Row88;
20078                   struct Row9;
20079                   struct Row1011;ˇ"#},
20080        vec![
20081            DiffHunkStatusKind::Modified,
20082            DiffHunkStatusKind::Modified,
20083            DiffHunkStatusKind::Modified,
20084            DiffHunkStatusKind::Modified,
20085            DiffHunkStatusKind::Modified,
20086            DiffHunkStatusKind::Modified,
20087        ],
20088        indoc! {r#"struct Row;
20089                   ˇstruct Row1;
20090                   struct Row2;
20091                   ˇ
20092                   struct Row4;
20093                   ˇstruct Row5;
20094                   struct Row6;
20095                   ˇ
20096                   struct Row8;
20097                   ˇstruct Row9;
20098                   struct Row10;ˇ"#},
20099        base_text,
20100        &mut cx,
20101    );
20102}
20103
20104#[gpui::test]
20105async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
20106    init_test(cx, |_| {});
20107    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20108    let base_text = indoc! {r#"
20109        one
20110
20111        two
20112        three
20113        "#};
20114
20115    cx.set_head_text(base_text);
20116    cx.set_state("\nˇ\n");
20117    cx.executor().run_until_parked();
20118    cx.update_editor(|editor, _window, cx| {
20119        editor.expand_selected_diff_hunks(cx);
20120    });
20121    cx.executor().run_until_parked();
20122    cx.update_editor(|editor, window, cx| {
20123        editor.backspace(&Default::default(), window, cx);
20124    });
20125    cx.run_until_parked();
20126    cx.assert_state_with_diff(
20127        indoc! {r#"
20128
20129        - two
20130        - threeˇ
20131        +
20132        "#}
20133        .to_string(),
20134    );
20135}
20136
20137#[gpui::test]
20138async fn test_deletion_reverts(cx: &mut TestAppContext) {
20139    init_test(cx, |_| {});
20140    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20141    let base_text = indoc! {r#"struct Row;
20142struct Row1;
20143struct Row2;
20144
20145struct Row4;
20146struct Row5;
20147struct Row6;
20148
20149struct Row8;
20150struct Row9;
20151struct Row10;"#};
20152
20153    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
20154    assert_hunk_revert(
20155        indoc! {r#"struct Row;
20156                   struct Row2;
20157
20158                   ˇstruct Row4;
20159                   struct Row5;
20160                   struct Row6;
20161                   ˇ
20162                   struct Row8;
20163                   struct Row10;"#},
20164        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20165        indoc! {r#"struct Row;
20166                   struct Row2;
20167
20168                   ˇstruct Row4;
20169                   struct Row5;
20170                   struct Row6;
20171                   ˇ
20172                   struct Row8;
20173                   struct Row10;"#},
20174        base_text,
20175        &mut cx,
20176    );
20177    assert_hunk_revert(
20178        indoc! {r#"struct Row;
20179                   struct Row2;
20180
20181                   «ˇstruct Row4;
20182                   struct» Row5;
20183                   «struct Row6;
20184                   ˇ»
20185                   struct Row8;
20186                   struct Row10;"#},
20187        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20188        indoc! {r#"struct Row;
20189                   struct Row2;
20190
20191                   «ˇstruct Row4;
20192                   struct» Row5;
20193                   «struct Row6;
20194                   ˇ»
20195                   struct Row8;
20196                   struct Row10;"#},
20197        base_text,
20198        &mut cx,
20199    );
20200
20201    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
20202    assert_hunk_revert(
20203        indoc! {r#"struct Row;
20204                   ˇstruct Row2;
20205
20206                   struct Row4;
20207                   struct Row5;
20208                   struct Row6;
20209
20210                   struct Row8;ˇ
20211                   struct Row10;"#},
20212        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20213        indoc! {r#"struct Row;
20214                   struct Row1;
20215                   ˇstruct Row2;
20216
20217                   struct Row4;
20218                   struct Row5;
20219                   struct Row6;
20220
20221                   struct Row8;ˇ
20222                   struct Row9;
20223                   struct Row10;"#},
20224        base_text,
20225        &mut cx,
20226    );
20227    assert_hunk_revert(
20228        indoc! {r#"struct Row;
20229                   struct Row2«ˇ;
20230                   struct Row4;
20231                   struct» Row5;
20232                   «struct Row6;
20233
20234                   struct Row8;ˇ»
20235                   struct Row10;"#},
20236        vec![
20237            DiffHunkStatusKind::Deleted,
20238            DiffHunkStatusKind::Deleted,
20239            DiffHunkStatusKind::Deleted,
20240        ],
20241        indoc! {r#"struct Row;
20242                   struct Row1;
20243                   struct Row2«ˇ;
20244
20245                   struct Row4;
20246                   struct» Row5;
20247                   «struct Row6;
20248
20249                   struct Row8;ˇ»
20250                   struct Row9;
20251                   struct Row10;"#},
20252        base_text,
20253        &mut cx,
20254    );
20255}
20256
20257#[gpui::test]
20258async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
20259    init_test(cx, |_| {});
20260
20261    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
20262    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
20263    let base_text_3 =
20264        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
20265
20266    let text_1 = edit_first_char_of_every_line(base_text_1);
20267    let text_2 = edit_first_char_of_every_line(base_text_2);
20268    let text_3 = edit_first_char_of_every_line(base_text_3);
20269
20270    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
20271    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
20272    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
20273
20274    let multibuffer = cx.new(|cx| {
20275        let mut multibuffer = MultiBuffer::new(ReadWrite);
20276        multibuffer.push_excerpts(
20277            buffer_1.clone(),
20278            [
20279                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20280                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20281                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20282            ],
20283            cx,
20284        );
20285        multibuffer.push_excerpts(
20286            buffer_2.clone(),
20287            [
20288                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20289                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20290                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20291            ],
20292            cx,
20293        );
20294        multibuffer.push_excerpts(
20295            buffer_3.clone(),
20296            [
20297                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20298                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20299                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20300            ],
20301            cx,
20302        );
20303        multibuffer
20304    });
20305
20306    let fs = FakeFs::new(cx.executor());
20307    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20308    let (editor, cx) = cx
20309        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20310    editor.update_in(cx, |editor, _window, cx| {
20311        for (buffer, diff_base) in [
20312            (buffer_1.clone(), base_text_1),
20313            (buffer_2.clone(), base_text_2),
20314            (buffer_3.clone(), base_text_3),
20315        ] {
20316            let diff = cx.new(|cx| {
20317                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20318            });
20319            editor
20320                .buffer
20321                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20322        }
20323    });
20324    cx.executor().run_until_parked();
20325
20326    editor.update_in(cx, |editor, window, cx| {
20327        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}");
20328        editor.select_all(&SelectAll, window, cx);
20329        editor.git_restore(&Default::default(), window, cx);
20330    });
20331    cx.executor().run_until_parked();
20332
20333    // When all ranges are selected, all buffer hunks are reverted.
20334    editor.update(cx, |editor, cx| {
20335        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");
20336    });
20337    buffer_1.update(cx, |buffer, _| {
20338        assert_eq!(buffer.text(), base_text_1);
20339    });
20340    buffer_2.update(cx, |buffer, _| {
20341        assert_eq!(buffer.text(), base_text_2);
20342    });
20343    buffer_3.update(cx, |buffer, _| {
20344        assert_eq!(buffer.text(), base_text_3);
20345    });
20346
20347    editor.update_in(cx, |editor, window, cx| {
20348        editor.undo(&Default::default(), window, cx);
20349    });
20350
20351    editor.update_in(cx, |editor, window, cx| {
20352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20353            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20354        });
20355        editor.git_restore(&Default::default(), window, cx);
20356    });
20357
20358    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20359    // but not affect buffer_2 and its related excerpts.
20360    editor.update(cx, |editor, cx| {
20361        assert_eq!(
20362            editor.text(cx),
20363            "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}"
20364        );
20365    });
20366    buffer_1.update(cx, |buffer, _| {
20367        assert_eq!(buffer.text(), base_text_1);
20368    });
20369    buffer_2.update(cx, |buffer, _| {
20370        assert_eq!(
20371            buffer.text(),
20372            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20373        );
20374    });
20375    buffer_3.update(cx, |buffer, _| {
20376        assert_eq!(
20377            buffer.text(),
20378            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20379        );
20380    });
20381
20382    fn edit_first_char_of_every_line(text: &str) -> String {
20383        text.split('\n')
20384            .map(|line| format!("X{}", &line[1..]))
20385            .collect::<Vec<_>>()
20386            .join("\n")
20387    }
20388}
20389
20390#[gpui::test]
20391async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20392    init_test(cx, |_| {});
20393
20394    let cols = 4;
20395    let rows = 10;
20396    let sample_text_1 = sample_text(rows, cols, 'a');
20397    assert_eq!(
20398        sample_text_1,
20399        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20400    );
20401    let sample_text_2 = sample_text(rows, cols, 'l');
20402    assert_eq!(
20403        sample_text_2,
20404        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20405    );
20406    let sample_text_3 = sample_text(rows, cols, 'v');
20407    assert_eq!(
20408        sample_text_3,
20409        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20410    );
20411
20412    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20413    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20414    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20415
20416    let multi_buffer = cx.new(|cx| {
20417        let mut multibuffer = MultiBuffer::new(ReadWrite);
20418        multibuffer.push_excerpts(
20419            buffer_1.clone(),
20420            [
20421                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20422                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20423                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20424            ],
20425            cx,
20426        );
20427        multibuffer.push_excerpts(
20428            buffer_2.clone(),
20429            [
20430                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20431                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20432                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20433            ],
20434            cx,
20435        );
20436        multibuffer.push_excerpts(
20437            buffer_3.clone(),
20438            [
20439                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20440                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20441                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20442            ],
20443            cx,
20444        );
20445        multibuffer
20446    });
20447
20448    let fs = FakeFs::new(cx.executor());
20449    fs.insert_tree(
20450        "/a",
20451        json!({
20452            "main.rs": sample_text_1,
20453            "other.rs": sample_text_2,
20454            "lib.rs": sample_text_3,
20455        }),
20456    )
20457    .await;
20458    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20459    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20460    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20461    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20462        Editor::new(
20463            EditorMode::full(),
20464            multi_buffer,
20465            Some(project.clone()),
20466            window,
20467            cx,
20468        )
20469    });
20470    let multibuffer_item_id = workspace
20471        .update(cx, |workspace, window, cx| {
20472            assert!(
20473                workspace.active_item(cx).is_none(),
20474                "active item should be None before the first item is added"
20475            );
20476            workspace.add_item_to_active_pane(
20477                Box::new(multi_buffer_editor.clone()),
20478                None,
20479                true,
20480                window,
20481                cx,
20482            );
20483            let active_item = workspace
20484                .active_item(cx)
20485                .expect("should have an active item after adding the multi buffer");
20486            assert_eq!(
20487                active_item.buffer_kind(cx),
20488                ItemBufferKind::Multibuffer,
20489                "A multi buffer was expected to active after adding"
20490            );
20491            active_item.item_id()
20492        })
20493        .unwrap();
20494    cx.executor().run_until_parked();
20495
20496    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20497        editor.change_selections(
20498            SelectionEffects::scroll(Autoscroll::Next),
20499            window,
20500            cx,
20501            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20502        );
20503        editor.open_excerpts(&OpenExcerpts, window, cx);
20504    });
20505    cx.executor().run_until_parked();
20506    let first_item_id = workspace
20507        .update(cx, |workspace, window, cx| {
20508            let active_item = workspace
20509                .active_item(cx)
20510                .expect("should have an active item after navigating into the 1st buffer");
20511            let first_item_id = active_item.item_id();
20512            assert_ne!(
20513                first_item_id, multibuffer_item_id,
20514                "Should navigate into the 1st buffer and activate it"
20515            );
20516            assert_eq!(
20517                active_item.buffer_kind(cx),
20518                ItemBufferKind::Singleton,
20519                "New active item should be a singleton buffer"
20520            );
20521            assert_eq!(
20522                active_item
20523                    .act_as::<Editor>(cx)
20524                    .expect("should have navigated into an editor for the 1st buffer")
20525                    .read(cx)
20526                    .text(cx),
20527                sample_text_1
20528            );
20529
20530            workspace
20531                .go_back(workspace.active_pane().downgrade(), window, cx)
20532                .detach_and_log_err(cx);
20533
20534            first_item_id
20535        })
20536        .unwrap();
20537    cx.executor().run_until_parked();
20538    workspace
20539        .update(cx, |workspace, _, cx| {
20540            let active_item = workspace
20541                .active_item(cx)
20542                .expect("should have an active item after navigating back");
20543            assert_eq!(
20544                active_item.item_id(),
20545                multibuffer_item_id,
20546                "Should navigate back to the multi buffer"
20547            );
20548            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20549        })
20550        .unwrap();
20551
20552    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20553        editor.change_selections(
20554            SelectionEffects::scroll(Autoscroll::Next),
20555            window,
20556            cx,
20557            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20558        );
20559        editor.open_excerpts(&OpenExcerpts, window, cx);
20560    });
20561    cx.executor().run_until_parked();
20562    let second_item_id = workspace
20563        .update(cx, |workspace, window, cx| {
20564            let active_item = workspace
20565                .active_item(cx)
20566                .expect("should have an active item after navigating into the 2nd buffer");
20567            let second_item_id = active_item.item_id();
20568            assert_ne!(
20569                second_item_id, multibuffer_item_id,
20570                "Should navigate away from the multibuffer"
20571            );
20572            assert_ne!(
20573                second_item_id, first_item_id,
20574                "Should navigate into the 2nd buffer and activate it"
20575            );
20576            assert_eq!(
20577                active_item.buffer_kind(cx),
20578                ItemBufferKind::Singleton,
20579                "New active item should be a singleton buffer"
20580            );
20581            assert_eq!(
20582                active_item
20583                    .act_as::<Editor>(cx)
20584                    .expect("should have navigated into an editor")
20585                    .read(cx)
20586                    .text(cx),
20587                sample_text_2
20588            );
20589
20590            workspace
20591                .go_back(workspace.active_pane().downgrade(), window, cx)
20592                .detach_and_log_err(cx);
20593
20594            second_item_id
20595        })
20596        .unwrap();
20597    cx.executor().run_until_parked();
20598    workspace
20599        .update(cx, |workspace, _, cx| {
20600            let active_item = workspace
20601                .active_item(cx)
20602                .expect("should have an active item after navigating back from the 2nd buffer");
20603            assert_eq!(
20604                active_item.item_id(),
20605                multibuffer_item_id,
20606                "Should navigate back from the 2nd buffer to the multi buffer"
20607            );
20608            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20609        })
20610        .unwrap();
20611
20612    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20613        editor.change_selections(
20614            SelectionEffects::scroll(Autoscroll::Next),
20615            window,
20616            cx,
20617            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20618        );
20619        editor.open_excerpts(&OpenExcerpts, window, cx);
20620    });
20621    cx.executor().run_until_parked();
20622    workspace
20623        .update(cx, |workspace, window, cx| {
20624            let active_item = workspace
20625                .active_item(cx)
20626                .expect("should have an active item after navigating into the 3rd buffer");
20627            let third_item_id = active_item.item_id();
20628            assert_ne!(
20629                third_item_id, multibuffer_item_id,
20630                "Should navigate into the 3rd buffer and activate it"
20631            );
20632            assert_ne!(third_item_id, first_item_id);
20633            assert_ne!(third_item_id, second_item_id);
20634            assert_eq!(
20635                active_item.buffer_kind(cx),
20636                ItemBufferKind::Singleton,
20637                "New active item should be a singleton buffer"
20638            );
20639            assert_eq!(
20640                active_item
20641                    .act_as::<Editor>(cx)
20642                    .expect("should have navigated into an editor")
20643                    .read(cx)
20644                    .text(cx),
20645                sample_text_3
20646            );
20647
20648            workspace
20649                .go_back(workspace.active_pane().downgrade(), window, cx)
20650                .detach_and_log_err(cx);
20651        })
20652        .unwrap();
20653    cx.executor().run_until_parked();
20654    workspace
20655        .update(cx, |workspace, _, cx| {
20656            let active_item = workspace
20657                .active_item(cx)
20658                .expect("should have an active item after navigating back from the 3rd buffer");
20659            assert_eq!(
20660                active_item.item_id(),
20661                multibuffer_item_id,
20662                "Should navigate back from the 3rd buffer to the multi buffer"
20663            );
20664            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20665        })
20666        .unwrap();
20667}
20668
20669#[gpui::test]
20670async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20671    init_test(cx, |_| {});
20672
20673    let mut cx = EditorTestContext::new(cx).await;
20674
20675    let diff_base = r#"
20676        use some::mod;
20677
20678        const A: u32 = 42;
20679
20680        fn main() {
20681            println!("hello");
20682
20683            println!("world");
20684        }
20685        "#
20686    .unindent();
20687
20688    cx.set_state(
20689        &r#"
20690        use some::modified;
20691
20692        ˇ
20693        fn main() {
20694            println!("hello there");
20695
20696            println!("around the");
20697            println!("world");
20698        }
20699        "#
20700        .unindent(),
20701    );
20702
20703    cx.set_head_text(&diff_base);
20704    executor.run_until_parked();
20705
20706    cx.update_editor(|editor, window, cx| {
20707        editor.go_to_next_hunk(&GoToHunk, window, cx);
20708        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20709    });
20710    executor.run_until_parked();
20711    cx.assert_state_with_diff(
20712        r#"
20713          use some::modified;
20714
20715
20716          fn main() {
20717        -     println!("hello");
20718        + ˇ    println!("hello there");
20719
20720              println!("around the");
20721              println!("world");
20722          }
20723        "#
20724        .unindent(),
20725    );
20726
20727    cx.update_editor(|editor, window, cx| {
20728        for _ in 0..2 {
20729            editor.go_to_next_hunk(&GoToHunk, window, cx);
20730            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20731        }
20732    });
20733    executor.run_until_parked();
20734    cx.assert_state_with_diff(
20735        r#"
20736        - use some::mod;
20737        + ˇuse some::modified;
20738
20739
20740          fn main() {
20741        -     println!("hello");
20742        +     println!("hello there");
20743
20744        +     println!("around the");
20745              println!("world");
20746          }
20747        "#
20748        .unindent(),
20749    );
20750
20751    cx.update_editor(|editor, window, cx| {
20752        editor.go_to_next_hunk(&GoToHunk, window, cx);
20753        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20754    });
20755    executor.run_until_parked();
20756    cx.assert_state_with_diff(
20757        r#"
20758        - use some::mod;
20759        + use some::modified;
20760
20761        - const A: u32 = 42;
20762          ˇ
20763          fn main() {
20764        -     println!("hello");
20765        +     println!("hello there");
20766
20767        +     println!("around the");
20768              println!("world");
20769          }
20770        "#
20771        .unindent(),
20772    );
20773
20774    cx.update_editor(|editor, window, cx| {
20775        editor.cancel(&Cancel, window, cx);
20776    });
20777
20778    cx.assert_state_with_diff(
20779        r#"
20780          use some::modified;
20781
20782          ˇ
20783          fn main() {
20784              println!("hello there");
20785
20786              println!("around the");
20787              println!("world");
20788          }
20789        "#
20790        .unindent(),
20791    );
20792}
20793
20794#[gpui::test]
20795async fn test_diff_base_change_with_expanded_diff_hunks(
20796    executor: BackgroundExecutor,
20797    cx: &mut TestAppContext,
20798) {
20799    init_test(cx, |_| {});
20800
20801    let mut cx = EditorTestContext::new(cx).await;
20802
20803    let diff_base = r#"
20804        use some::mod1;
20805        use some::mod2;
20806
20807        const A: u32 = 42;
20808        const B: u32 = 42;
20809        const C: u32 = 42;
20810
20811        fn main() {
20812            println!("hello");
20813
20814            println!("world");
20815        }
20816        "#
20817    .unindent();
20818
20819    cx.set_state(
20820        &r#"
20821        use some::mod2;
20822
20823        const A: u32 = 42;
20824        const C: u32 = 42;
20825
20826        fn main(ˇ) {
20827            //println!("hello");
20828
20829            println!("world");
20830            //
20831            //
20832        }
20833        "#
20834        .unindent(),
20835    );
20836
20837    cx.set_head_text(&diff_base);
20838    executor.run_until_parked();
20839
20840    cx.update_editor(|editor, window, cx| {
20841        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20842    });
20843    executor.run_until_parked();
20844    cx.assert_state_with_diff(
20845        r#"
20846        - use some::mod1;
20847          use some::mod2;
20848
20849          const A: u32 = 42;
20850        - const B: u32 = 42;
20851          const C: u32 = 42;
20852
20853          fn main(ˇ) {
20854        -     println!("hello");
20855        +     //println!("hello");
20856
20857              println!("world");
20858        +     //
20859        +     //
20860          }
20861        "#
20862        .unindent(),
20863    );
20864
20865    cx.set_head_text("new diff base!");
20866    executor.run_until_parked();
20867    cx.assert_state_with_diff(
20868        r#"
20869        - new diff base!
20870        + use some::mod2;
20871        +
20872        + const A: u32 = 42;
20873        + const C: u32 = 42;
20874        +
20875        + fn main(ˇ) {
20876        +     //println!("hello");
20877        +
20878        +     println!("world");
20879        +     //
20880        +     //
20881        + }
20882        "#
20883        .unindent(),
20884    );
20885}
20886
20887#[gpui::test]
20888async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20889    init_test(cx, |_| {});
20890
20891    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20892    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20893    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20894    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20895    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20896    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20897
20898    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20899    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20900    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20901
20902    let multi_buffer = cx.new(|cx| {
20903        let mut multibuffer = MultiBuffer::new(ReadWrite);
20904        multibuffer.push_excerpts(
20905            buffer_1.clone(),
20906            [
20907                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20908                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20909                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20910            ],
20911            cx,
20912        );
20913        multibuffer.push_excerpts(
20914            buffer_2.clone(),
20915            [
20916                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20917                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20918                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20919            ],
20920            cx,
20921        );
20922        multibuffer.push_excerpts(
20923            buffer_3.clone(),
20924            [
20925                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20926                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20927                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20928            ],
20929            cx,
20930        );
20931        multibuffer
20932    });
20933
20934    let editor =
20935        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20936    editor
20937        .update(cx, |editor, _window, cx| {
20938            for (buffer, diff_base) in [
20939                (buffer_1.clone(), file_1_old),
20940                (buffer_2.clone(), file_2_old),
20941                (buffer_3.clone(), file_3_old),
20942            ] {
20943                let diff = cx.new(|cx| {
20944                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20945                });
20946                editor
20947                    .buffer
20948                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20949            }
20950        })
20951        .unwrap();
20952
20953    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20954    cx.run_until_parked();
20955
20956    cx.assert_editor_state(
20957        &"
20958            ˇaaa
20959            ccc
20960            ddd
20961
20962            ggg
20963            hhh
20964
20965
20966            lll
20967            mmm
20968            NNN
20969
20970            qqq
20971            rrr
20972
20973            uuu
20974            111
20975            222
20976            333
20977
20978            666
20979            777
20980
20981            000
20982            !!!"
20983        .unindent(),
20984    );
20985
20986    cx.update_editor(|editor, window, cx| {
20987        editor.select_all(&SelectAll, window, cx);
20988        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20989    });
20990    cx.executor().run_until_parked();
20991
20992    cx.assert_state_with_diff(
20993        "
20994            «aaa
20995          - bbb
20996            ccc
20997            ddd
20998
20999            ggg
21000            hhh
21001
21002
21003            lll
21004            mmm
21005          - nnn
21006          + NNN
21007
21008            qqq
21009            rrr
21010
21011            uuu
21012            111
21013            222
21014            333
21015
21016          + 666
21017            777
21018
21019            000
21020            !!!ˇ»"
21021            .unindent(),
21022    );
21023}
21024
21025#[gpui::test]
21026async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
21027    init_test(cx, |_| {});
21028
21029    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
21030    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
21031
21032    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
21033    let multi_buffer = cx.new(|cx| {
21034        let mut multibuffer = MultiBuffer::new(ReadWrite);
21035        multibuffer.push_excerpts(
21036            buffer.clone(),
21037            [
21038                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
21039                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
21040                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
21041            ],
21042            cx,
21043        );
21044        multibuffer
21045    });
21046
21047    let editor =
21048        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21049    editor
21050        .update(cx, |editor, _window, cx| {
21051            let diff = cx.new(|cx| {
21052                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
21053            });
21054            editor
21055                .buffer
21056                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
21057        })
21058        .unwrap();
21059
21060    let mut cx = EditorTestContext::for_editor(editor, cx).await;
21061    cx.run_until_parked();
21062
21063    cx.update_editor(|editor, window, cx| {
21064        editor.expand_all_diff_hunks(&Default::default(), window, cx)
21065    });
21066    cx.executor().run_until_parked();
21067
21068    // When the start of a hunk coincides with the start of its excerpt,
21069    // the hunk is expanded. When the start of a hunk is earlier than
21070    // the start of its excerpt, the hunk is not expanded.
21071    cx.assert_state_with_diff(
21072        "
21073            ˇaaa
21074          - bbb
21075          + BBB
21076
21077          - ddd
21078          - eee
21079          + DDD
21080          + EEE
21081            fff
21082
21083            iii
21084        "
21085        .unindent(),
21086    );
21087}
21088
21089#[gpui::test]
21090async fn test_edits_around_expanded_insertion_hunks(
21091    executor: BackgroundExecutor,
21092    cx: &mut TestAppContext,
21093) {
21094    init_test(cx, |_| {});
21095
21096    let mut cx = EditorTestContext::new(cx).await;
21097
21098    let diff_base = r#"
21099        use some::mod1;
21100        use some::mod2;
21101
21102        const A: u32 = 42;
21103
21104        fn main() {
21105            println!("hello");
21106
21107            println!("world");
21108        }
21109        "#
21110    .unindent();
21111    executor.run_until_parked();
21112    cx.set_state(
21113        &r#"
21114        use some::mod1;
21115        use some::mod2;
21116
21117        const A: u32 = 42;
21118        const B: u32 = 42;
21119        const C: u32 = 42;
21120        ˇ
21121
21122        fn main() {
21123            println!("hello");
21124
21125            println!("world");
21126        }
21127        "#
21128        .unindent(),
21129    );
21130
21131    cx.set_head_text(&diff_base);
21132    executor.run_until_parked();
21133
21134    cx.update_editor(|editor, window, cx| {
21135        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21136    });
21137    executor.run_until_parked();
21138
21139    cx.assert_state_with_diff(
21140        r#"
21141        use some::mod1;
21142        use some::mod2;
21143
21144        const A: u32 = 42;
21145      + const B: u32 = 42;
21146      + const C: u32 = 42;
21147      + ˇ
21148
21149        fn main() {
21150            println!("hello");
21151
21152            println!("world");
21153        }
21154      "#
21155        .unindent(),
21156    );
21157
21158    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
21159    executor.run_until_parked();
21160
21161    cx.assert_state_with_diff(
21162        r#"
21163        use some::mod1;
21164        use some::mod2;
21165
21166        const A: u32 = 42;
21167      + const B: u32 = 42;
21168      + const C: u32 = 42;
21169      + const D: u32 = 42;
21170      + ˇ
21171
21172        fn main() {
21173            println!("hello");
21174
21175            println!("world");
21176        }
21177      "#
21178        .unindent(),
21179    );
21180
21181    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
21182    executor.run_until_parked();
21183
21184    cx.assert_state_with_diff(
21185        r#"
21186        use some::mod1;
21187        use some::mod2;
21188
21189        const A: u32 = 42;
21190      + const B: u32 = 42;
21191      + const C: u32 = 42;
21192      + const D: u32 = 42;
21193      + const E: u32 = 42;
21194      + ˇ
21195
21196        fn main() {
21197            println!("hello");
21198
21199            println!("world");
21200        }
21201      "#
21202        .unindent(),
21203    );
21204
21205    cx.update_editor(|editor, window, cx| {
21206        editor.delete_line(&DeleteLine, window, cx);
21207    });
21208    executor.run_until_parked();
21209
21210    cx.assert_state_with_diff(
21211        r#"
21212        use some::mod1;
21213        use some::mod2;
21214
21215        const A: u32 = 42;
21216      + const B: u32 = 42;
21217      + const C: u32 = 42;
21218      + const D: u32 = 42;
21219      + const E: u32 = 42;
21220        ˇ
21221        fn main() {
21222            println!("hello");
21223
21224            println!("world");
21225        }
21226      "#
21227        .unindent(),
21228    );
21229
21230    cx.update_editor(|editor, window, cx| {
21231        editor.move_up(&MoveUp, window, cx);
21232        editor.delete_line(&DeleteLine, window, cx);
21233        editor.move_up(&MoveUp, window, cx);
21234        editor.delete_line(&DeleteLine, window, cx);
21235        editor.move_up(&MoveUp, window, cx);
21236        editor.delete_line(&DeleteLine, window, cx);
21237    });
21238    executor.run_until_parked();
21239    cx.assert_state_with_diff(
21240        r#"
21241        use some::mod1;
21242        use some::mod2;
21243
21244        const A: u32 = 42;
21245      + const B: u32 = 42;
21246        ˇ
21247        fn main() {
21248            println!("hello");
21249
21250            println!("world");
21251        }
21252      "#
21253        .unindent(),
21254    );
21255
21256    cx.update_editor(|editor, window, cx| {
21257        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
21258        editor.delete_line(&DeleteLine, window, cx);
21259    });
21260    executor.run_until_parked();
21261    cx.assert_state_with_diff(
21262        r#"
21263        ˇ
21264        fn main() {
21265            println!("hello");
21266
21267            println!("world");
21268        }
21269      "#
21270        .unindent(),
21271    );
21272}
21273
21274#[gpui::test]
21275async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21276    init_test(cx, |_| {});
21277
21278    let mut cx = EditorTestContext::new(cx).await;
21279    cx.set_head_text(indoc! { "
21280        one
21281        two
21282        three
21283        four
21284        five
21285        "
21286    });
21287    cx.set_state(indoc! { "
21288        one
21289        ˇthree
21290        five
21291    "});
21292    cx.run_until_parked();
21293    cx.update_editor(|editor, window, cx| {
21294        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21295    });
21296    cx.assert_state_with_diff(
21297        indoc! { "
21298        one
21299      - two
21300        ˇthree
21301      - four
21302        five
21303    "}
21304        .to_string(),
21305    );
21306    cx.update_editor(|editor, window, cx| {
21307        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21308    });
21309
21310    cx.assert_state_with_diff(
21311        indoc! { "
21312        one
21313        ˇthree
21314        five
21315    "}
21316        .to_string(),
21317    );
21318
21319    cx.update_editor(|editor, window, cx| {
21320        editor.move_up(&MoveUp, window, cx);
21321        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21322    });
21323    cx.assert_state_with_diff(
21324        indoc! { "
21325        ˇone
21326      - two
21327        three
21328        five
21329    "}
21330        .to_string(),
21331    );
21332
21333    cx.update_editor(|editor, window, cx| {
21334        editor.move_down(&MoveDown, window, cx);
21335        editor.move_down(&MoveDown, window, cx);
21336        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21337    });
21338    cx.assert_state_with_diff(
21339        indoc! { "
21340        one
21341      - two
21342        ˇthree
21343      - four
21344        five
21345    "}
21346        .to_string(),
21347    );
21348
21349    cx.set_state(indoc! { "
21350        one
21351        ˇTWO
21352        three
21353        four
21354        five
21355    "});
21356    cx.run_until_parked();
21357    cx.update_editor(|editor, window, cx| {
21358        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21359    });
21360
21361    cx.assert_state_with_diff(
21362        indoc! { "
21363            one
21364          - two
21365          + ˇTWO
21366            three
21367            four
21368            five
21369        "}
21370        .to_string(),
21371    );
21372    cx.update_editor(|editor, window, cx| {
21373        editor.move_up(&Default::default(), window, cx);
21374        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21375    });
21376    cx.assert_state_with_diff(
21377        indoc! { "
21378            one
21379            ˇTWO
21380            three
21381            four
21382            five
21383        "}
21384        .to_string(),
21385    );
21386}
21387
21388#[gpui::test]
21389async fn test_toggling_adjacent_diff_hunks_2(
21390    executor: BackgroundExecutor,
21391    cx: &mut TestAppContext,
21392) {
21393    init_test(cx, |_| {});
21394
21395    let mut cx = EditorTestContext::new(cx).await;
21396
21397    let diff_base = r#"
21398        lineA
21399        lineB
21400        lineC
21401        lineD
21402        "#
21403    .unindent();
21404
21405    cx.set_state(
21406        &r#"
21407        ˇlineA1
21408        lineB
21409        lineD
21410        "#
21411        .unindent(),
21412    );
21413    cx.set_head_text(&diff_base);
21414    executor.run_until_parked();
21415
21416    cx.update_editor(|editor, window, cx| {
21417        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21418    });
21419    executor.run_until_parked();
21420    cx.assert_state_with_diff(
21421        r#"
21422        - lineA
21423        + ˇlineA1
21424          lineB
21425          lineD
21426        "#
21427        .unindent(),
21428    );
21429
21430    cx.update_editor(|editor, window, cx| {
21431        editor.move_down(&MoveDown, window, cx);
21432        editor.move_right(&MoveRight, window, cx);
21433        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21434    });
21435    executor.run_until_parked();
21436    cx.assert_state_with_diff(
21437        r#"
21438        - lineA
21439        + lineA1
21440          lˇineB
21441        - lineC
21442          lineD
21443        "#
21444        .unindent(),
21445    );
21446}
21447
21448#[gpui::test]
21449async fn test_edits_around_expanded_deletion_hunks(
21450    executor: BackgroundExecutor,
21451    cx: &mut TestAppContext,
21452) {
21453    init_test(cx, |_| {});
21454
21455    let mut cx = EditorTestContext::new(cx).await;
21456
21457    let diff_base = r#"
21458        use some::mod1;
21459        use some::mod2;
21460
21461        const A: u32 = 42;
21462        const B: u32 = 42;
21463        const C: u32 = 42;
21464
21465
21466        fn main() {
21467            println!("hello");
21468
21469            println!("world");
21470        }
21471    "#
21472    .unindent();
21473    executor.run_until_parked();
21474    cx.set_state(
21475        &r#"
21476        use some::mod1;
21477        use some::mod2;
21478
21479        ˇconst B: u32 = 42;
21480        const C: u32 = 42;
21481
21482
21483        fn main() {
21484            println!("hello");
21485
21486            println!("world");
21487        }
21488        "#
21489        .unindent(),
21490    );
21491
21492    cx.set_head_text(&diff_base);
21493    executor.run_until_parked();
21494
21495    cx.update_editor(|editor, window, cx| {
21496        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21497    });
21498    executor.run_until_parked();
21499
21500    cx.assert_state_with_diff(
21501        r#"
21502        use some::mod1;
21503        use some::mod2;
21504
21505      - const A: u32 = 42;
21506        ˇconst B: u32 = 42;
21507        const C: u32 = 42;
21508
21509
21510        fn main() {
21511            println!("hello");
21512
21513            println!("world");
21514        }
21515      "#
21516        .unindent(),
21517    );
21518
21519    cx.update_editor(|editor, window, cx| {
21520        editor.delete_line(&DeleteLine, window, cx);
21521    });
21522    executor.run_until_parked();
21523    cx.assert_state_with_diff(
21524        r#"
21525        use some::mod1;
21526        use some::mod2;
21527
21528      - const A: u32 = 42;
21529      - const B: u32 = 42;
21530        ˇconst C: u32 = 42;
21531
21532
21533        fn main() {
21534            println!("hello");
21535
21536            println!("world");
21537        }
21538      "#
21539        .unindent(),
21540    );
21541
21542    cx.update_editor(|editor, window, cx| {
21543        editor.delete_line(&DeleteLine, window, cx);
21544    });
21545    executor.run_until_parked();
21546    cx.assert_state_with_diff(
21547        r#"
21548        use some::mod1;
21549        use some::mod2;
21550
21551      - const A: u32 = 42;
21552      - const B: u32 = 42;
21553      - const C: u32 = 42;
21554        ˇ
21555
21556        fn main() {
21557            println!("hello");
21558
21559            println!("world");
21560        }
21561      "#
21562        .unindent(),
21563    );
21564
21565    cx.update_editor(|editor, window, cx| {
21566        editor.handle_input("replacement", window, cx);
21567    });
21568    executor.run_until_parked();
21569    cx.assert_state_with_diff(
21570        r#"
21571        use some::mod1;
21572        use some::mod2;
21573
21574      - const A: u32 = 42;
21575      - const B: u32 = 42;
21576      - const C: u32 = 42;
21577      -
21578      + replacementˇ
21579
21580        fn main() {
21581            println!("hello");
21582
21583            println!("world");
21584        }
21585      "#
21586        .unindent(),
21587    );
21588}
21589
21590#[gpui::test]
21591async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21592    init_test(cx, |_| {});
21593
21594    let mut cx = EditorTestContext::new(cx).await;
21595
21596    let base_text = r#"
21597        one
21598        two
21599        three
21600        four
21601        five
21602    "#
21603    .unindent();
21604    executor.run_until_parked();
21605    cx.set_state(
21606        &r#"
21607        one
21608        two
21609        fˇour
21610        five
21611        "#
21612        .unindent(),
21613    );
21614
21615    cx.set_head_text(&base_text);
21616    executor.run_until_parked();
21617
21618    cx.update_editor(|editor, window, cx| {
21619        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21620    });
21621    executor.run_until_parked();
21622
21623    cx.assert_state_with_diff(
21624        r#"
21625          one
21626          two
21627        - three
21628          fˇour
21629          five
21630        "#
21631        .unindent(),
21632    );
21633
21634    cx.update_editor(|editor, window, cx| {
21635        editor.backspace(&Backspace, window, cx);
21636        editor.backspace(&Backspace, window, cx);
21637    });
21638    executor.run_until_parked();
21639    cx.assert_state_with_diff(
21640        r#"
21641          one
21642          two
21643        - threeˇ
21644        - four
21645        + our
21646          five
21647        "#
21648        .unindent(),
21649    );
21650}
21651
21652#[gpui::test]
21653async fn test_edit_after_expanded_modification_hunk(
21654    executor: BackgroundExecutor,
21655    cx: &mut TestAppContext,
21656) {
21657    init_test(cx, |_| {});
21658
21659    let mut cx = EditorTestContext::new(cx).await;
21660
21661    let diff_base = r#"
21662        use some::mod1;
21663        use some::mod2;
21664
21665        const A: u32 = 42;
21666        const B: u32 = 42;
21667        const C: u32 = 42;
21668        const D: u32 = 42;
21669
21670
21671        fn main() {
21672            println!("hello");
21673
21674            println!("world");
21675        }"#
21676    .unindent();
21677
21678    cx.set_state(
21679        &r#"
21680        use some::mod1;
21681        use some::mod2;
21682
21683        const A: u32 = 42;
21684        const B: u32 = 42;
21685        const C: u32 = 43ˇ
21686        const D: u32 = 42;
21687
21688
21689        fn main() {
21690            println!("hello");
21691
21692            println!("world");
21693        }"#
21694        .unindent(),
21695    );
21696
21697    cx.set_head_text(&diff_base);
21698    executor.run_until_parked();
21699    cx.update_editor(|editor, window, cx| {
21700        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21701    });
21702    executor.run_until_parked();
21703
21704    cx.assert_state_with_diff(
21705        r#"
21706        use some::mod1;
21707        use some::mod2;
21708
21709        const A: u32 = 42;
21710        const B: u32 = 42;
21711      - const C: u32 = 42;
21712      + const C: u32 = 43ˇ
21713        const D: u32 = 42;
21714
21715
21716        fn main() {
21717            println!("hello");
21718
21719            println!("world");
21720        }"#
21721        .unindent(),
21722    );
21723
21724    cx.update_editor(|editor, window, cx| {
21725        editor.handle_input("\nnew_line\n", window, cx);
21726    });
21727    executor.run_until_parked();
21728
21729    cx.assert_state_with_diff(
21730        r#"
21731        use some::mod1;
21732        use some::mod2;
21733
21734        const A: u32 = 42;
21735        const B: u32 = 42;
21736      - const C: u32 = 42;
21737      + const C: u32 = 43
21738      + new_line
21739      + ˇ
21740        const D: u32 = 42;
21741
21742
21743        fn main() {
21744            println!("hello");
21745
21746            println!("world");
21747        }"#
21748        .unindent(),
21749    );
21750}
21751
21752#[gpui::test]
21753async fn test_stage_and_unstage_added_file_hunk(
21754    executor: BackgroundExecutor,
21755    cx: &mut TestAppContext,
21756) {
21757    init_test(cx, |_| {});
21758
21759    let mut cx = EditorTestContext::new(cx).await;
21760    cx.update_editor(|editor, _, cx| {
21761        editor.set_expand_all_diff_hunks(cx);
21762    });
21763
21764    let working_copy = r#"
21765            ˇfn main() {
21766                println!("hello, world!");
21767            }
21768        "#
21769    .unindent();
21770
21771    cx.set_state(&working_copy);
21772    executor.run_until_parked();
21773
21774    cx.assert_state_with_diff(
21775        r#"
21776            + ˇfn main() {
21777            +     println!("hello, world!");
21778            + }
21779        "#
21780        .unindent(),
21781    );
21782    cx.assert_index_text(None);
21783
21784    cx.update_editor(|editor, window, cx| {
21785        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21786    });
21787    executor.run_until_parked();
21788    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21789    cx.assert_state_with_diff(
21790        r#"
21791            + ˇfn main() {
21792            +     println!("hello, world!");
21793            + }
21794        "#
21795        .unindent(),
21796    );
21797
21798    cx.update_editor(|editor, window, cx| {
21799        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21800    });
21801    executor.run_until_parked();
21802    cx.assert_index_text(None);
21803}
21804
21805async fn setup_indent_guides_editor(
21806    text: &str,
21807    cx: &mut TestAppContext,
21808) -> (BufferId, EditorTestContext) {
21809    init_test(cx, |_| {});
21810
21811    let mut cx = EditorTestContext::new(cx).await;
21812
21813    let buffer_id = cx.update_editor(|editor, window, cx| {
21814        editor.set_text(text, window, cx);
21815        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21816
21817        buffer_ids[0]
21818    });
21819
21820    (buffer_id, cx)
21821}
21822
21823fn assert_indent_guides(
21824    range: Range<u32>,
21825    expected: Vec<IndentGuide>,
21826    active_indices: Option<Vec<usize>>,
21827    cx: &mut EditorTestContext,
21828) {
21829    let indent_guides = cx.update_editor(|editor, window, cx| {
21830        let snapshot = editor.snapshot(window, cx).display_snapshot;
21831        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21832            editor,
21833            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21834            true,
21835            &snapshot,
21836            cx,
21837        );
21838
21839        indent_guides.sort_by(|a, b| {
21840            a.depth.cmp(&b.depth).then(
21841                a.start_row
21842                    .cmp(&b.start_row)
21843                    .then(a.end_row.cmp(&b.end_row)),
21844            )
21845        });
21846        indent_guides
21847    });
21848
21849    if let Some(expected) = active_indices {
21850        let active_indices = cx.update_editor(|editor, window, cx| {
21851            let snapshot = editor.snapshot(window, cx).display_snapshot;
21852            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21853        });
21854
21855        assert_eq!(
21856            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21857            expected,
21858            "Active indent guide indices do not match"
21859        );
21860    }
21861
21862    assert_eq!(indent_guides, expected, "Indent guides do not match");
21863}
21864
21865fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21866    IndentGuide {
21867        buffer_id,
21868        start_row: MultiBufferRow(start_row),
21869        end_row: MultiBufferRow(end_row),
21870        depth,
21871        tab_size: 4,
21872        settings: IndentGuideSettings {
21873            enabled: true,
21874            line_width: 1,
21875            active_line_width: 1,
21876            coloring: IndentGuideColoring::default(),
21877            background_coloring: IndentGuideBackgroundColoring::default(),
21878        },
21879    }
21880}
21881
21882#[gpui::test]
21883async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21884    let (buffer_id, mut cx) = setup_indent_guides_editor(
21885        &"
21886        fn main() {
21887            let a = 1;
21888        }"
21889        .unindent(),
21890        cx,
21891    )
21892    .await;
21893
21894    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21895}
21896
21897#[gpui::test]
21898async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21899    let (buffer_id, mut cx) = setup_indent_guides_editor(
21900        &"
21901        fn main() {
21902            let a = 1;
21903            let b = 2;
21904        }"
21905        .unindent(),
21906        cx,
21907    )
21908    .await;
21909
21910    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21911}
21912
21913#[gpui::test]
21914async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21915    let (buffer_id, mut cx) = setup_indent_guides_editor(
21916        &"
21917        fn main() {
21918            let a = 1;
21919            if a == 3 {
21920                let b = 2;
21921            } else {
21922                let c = 3;
21923            }
21924        }"
21925        .unindent(),
21926        cx,
21927    )
21928    .await;
21929
21930    assert_indent_guides(
21931        0..8,
21932        vec![
21933            indent_guide(buffer_id, 1, 6, 0),
21934            indent_guide(buffer_id, 3, 3, 1),
21935            indent_guide(buffer_id, 5, 5, 1),
21936        ],
21937        None,
21938        &mut cx,
21939    );
21940}
21941
21942#[gpui::test]
21943async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21944    let (buffer_id, mut cx) = setup_indent_guides_editor(
21945        &"
21946        fn main() {
21947            let a = 1;
21948                let b = 2;
21949            let c = 3;
21950        }"
21951        .unindent(),
21952        cx,
21953    )
21954    .await;
21955
21956    assert_indent_guides(
21957        0..5,
21958        vec![
21959            indent_guide(buffer_id, 1, 3, 0),
21960            indent_guide(buffer_id, 2, 2, 1),
21961        ],
21962        None,
21963        &mut cx,
21964    );
21965}
21966
21967#[gpui::test]
21968async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21969    let (buffer_id, mut cx) = setup_indent_guides_editor(
21970        &"
21971        fn main() {
21972            let a = 1;
21973
21974            let c = 3;
21975        }"
21976        .unindent(),
21977        cx,
21978    )
21979    .await;
21980
21981    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21982}
21983
21984#[gpui::test]
21985async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21986    let (buffer_id, mut cx) = setup_indent_guides_editor(
21987        &"
21988        fn main() {
21989            let a = 1;
21990
21991            let c = 3;
21992
21993            if a == 3 {
21994                let b = 2;
21995            } else {
21996                let c = 3;
21997            }
21998        }"
21999        .unindent(),
22000        cx,
22001    )
22002    .await;
22003
22004    assert_indent_guides(
22005        0..11,
22006        vec![
22007            indent_guide(buffer_id, 1, 9, 0),
22008            indent_guide(buffer_id, 6, 6, 1),
22009            indent_guide(buffer_id, 8, 8, 1),
22010        ],
22011        None,
22012        &mut cx,
22013    );
22014}
22015
22016#[gpui::test]
22017async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
22018    let (buffer_id, mut cx) = setup_indent_guides_editor(
22019        &"
22020        fn main() {
22021            let a = 1;
22022
22023            let c = 3;
22024
22025            if a == 3 {
22026                let b = 2;
22027            } else {
22028                let c = 3;
22029            }
22030        }"
22031        .unindent(),
22032        cx,
22033    )
22034    .await;
22035
22036    assert_indent_guides(
22037        1..11,
22038        vec![
22039            indent_guide(buffer_id, 1, 9, 0),
22040            indent_guide(buffer_id, 6, 6, 1),
22041            indent_guide(buffer_id, 8, 8, 1),
22042        ],
22043        None,
22044        &mut cx,
22045    );
22046}
22047
22048#[gpui::test]
22049async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
22050    let (buffer_id, mut cx) = setup_indent_guides_editor(
22051        &"
22052        fn main() {
22053            let a = 1;
22054
22055            let c = 3;
22056
22057            if a == 3 {
22058                let b = 2;
22059            } else {
22060                let c = 3;
22061            }
22062        }"
22063        .unindent(),
22064        cx,
22065    )
22066    .await;
22067
22068    assert_indent_guides(
22069        1..10,
22070        vec![
22071            indent_guide(buffer_id, 1, 9, 0),
22072            indent_guide(buffer_id, 6, 6, 1),
22073            indent_guide(buffer_id, 8, 8, 1),
22074        ],
22075        None,
22076        &mut cx,
22077    );
22078}
22079
22080#[gpui::test]
22081async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
22082    let (buffer_id, mut cx) = setup_indent_guides_editor(
22083        &"
22084        fn main() {
22085            if a {
22086                b(
22087                    c,
22088                    d,
22089                )
22090            } else {
22091                e(
22092                    f
22093                )
22094            }
22095        }"
22096        .unindent(),
22097        cx,
22098    )
22099    .await;
22100
22101    assert_indent_guides(
22102        0..11,
22103        vec![
22104            indent_guide(buffer_id, 1, 10, 0),
22105            indent_guide(buffer_id, 2, 5, 1),
22106            indent_guide(buffer_id, 7, 9, 1),
22107            indent_guide(buffer_id, 3, 4, 2),
22108            indent_guide(buffer_id, 8, 8, 2),
22109        ],
22110        None,
22111        &mut cx,
22112    );
22113
22114    cx.update_editor(|editor, window, cx| {
22115        editor.fold_at(MultiBufferRow(2), window, cx);
22116        assert_eq!(
22117            editor.display_text(cx),
22118            "
22119            fn main() {
22120                if a {
22121                    b(⋯
22122                    )
22123                } else {
22124                    e(
22125                        f
22126                    )
22127                }
22128            }"
22129            .unindent()
22130        );
22131    });
22132
22133    assert_indent_guides(
22134        0..11,
22135        vec![
22136            indent_guide(buffer_id, 1, 10, 0),
22137            indent_guide(buffer_id, 2, 5, 1),
22138            indent_guide(buffer_id, 7, 9, 1),
22139            indent_guide(buffer_id, 8, 8, 2),
22140        ],
22141        None,
22142        &mut cx,
22143    );
22144}
22145
22146#[gpui::test]
22147async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
22148    let (buffer_id, mut cx) = setup_indent_guides_editor(
22149        &"
22150        block1
22151            block2
22152                block3
22153                    block4
22154            block2
22155        block1
22156        block1"
22157            .unindent(),
22158        cx,
22159    )
22160    .await;
22161
22162    assert_indent_guides(
22163        1..10,
22164        vec![
22165            indent_guide(buffer_id, 1, 4, 0),
22166            indent_guide(buffer_id, 2, 3, 1),
22167            indent_guide(buffer_id, 3, 3, 2),
22168        ],
22169        None,
22170        &mut cx,
22171    );
22172}
22173
22174#[gpui::test]
22175async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
22176    let (buffer_id, mut cx) = setup_indent_guides_editor(
22177        &"
22178        block1
22179            block2
22180                block3
22181
22182        block1
22183        block1"
22184            .unindent(),
22185        cx,
22186    )
22187    .await;
22188
22189    assert_indent_guides(
22190        0..6,
22191        vec![
22192            indent_guide(buffer_id, 1, 2, 0),
22193            indent_guide(buffer_id, 2, 2, 1),
22194        ],
22195        None,
22196        &mut cx,
22197    );
22198}
22199
22200#[gpui::test]
22201async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
22202    let (buffer_id, mut cx) = setup_indent_guides_editor(
22203        &"
22204        function component() {
22205        \treturn (
22206        \t\t\t
22207        \t\t<div>
22208        \t\t\t<abc></abc>
22209        \t\t</div>
22210        \t)
22211        }"
22212        .unindent(),
22213        cx,
22214    )
22215    .await;
22216
22217    assert_indent_guides(
22218        0..8,
22219        vec![
22220            indent_guide(buffer_id, 1, 6, 0),
22221            indent_guide(buffer_id, 2, 5, 1),
22222            indent_guide(buffer_id, 4, 4, 2),
22223        ],
22224        None,
22225        &mut cx,
22226    );
22227}
22228
22229#[gpui::test]
22230async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
22231    let (buffer_id, mut cx) = setup_indent_guides_editor(
22232        &"
22233        function component() {
22234        \treturn (
22235        \t
22236        \t\t<div>
22237        \t\t\t<abc></abc>
22238        \t\t</div>
22239        \t)
22240        }"
22241        .unindent(),
22242        cx,
22243    )
22244    .await;
22245
22246    assert_indent_guides(
22247        0..8,
22248        vec![
22249            indent_guide(buffer_id, 1, 6, 0),
22250            indent_guide(buffer_id, 2, 5, 1),
22251            indent_guide(buffer_id, 4, 4, 2),
22252        ],
22253        None,
22254        &mut cx,
22255    );
22256}
22257
22258#[gpui::test]
22259async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
22260    let (buffer_id, mut cx) = setup_indent_guides_editor(
22261        &"
22262        block1
22263
22264
22265
22266            block2
22267        "
22268        .unindent(),
22269        cx,
22270    )
22271    .await;
22272
22273    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22274}
22275
22276#[gpui::test]
22277async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22278    let (buffer_id, mut cx) = setup_indent_guides_editor(
22279        &"
22280        def a:
22281        \tb = 3
22282        \tif True:
22283        \t\tc = 4
22284        \t\td = 5
22285        \tprint(b)
22286        "
22287        .unindent(),
22288        cx,
22289    )
22290    .await;
22291
22292    assert_indent_guides(
22293        0..6,
22294        vec![
22295            indent_guide(buffer_id, 1, 5, 0),
22296            indent_guide(buffer_id, 3, 4, 1),
22297        ],
22298        None,
22299        &mut cx,
22300    );
22301}
22302
22303#[gpui::test]
22304async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22305    let (buffer_id, mut cx) = setup_indent_guides_editor(
22306        &"
22307    fn main() {
22308        let a = 1;
22309    }"
22310        .unindent(),
22311        cx,
22312    )
22313    .await;
22314
22315    cx.update_editor(|editor, window, cx| {
22316        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22317            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22318        });
22319    });
22320
22321    assert_indent_guides(
22322        0..3,
22323        vec![indent_guide(buffer_id, 1, 1, 0)],
22324        Some(vec![0]),
22325        &mut cx,
22326    );
22327}
22328
22329#[gpui::test]
22330async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22331    let (buffer_id, mut cx) = setup_indent_guides_editor(
22332        &"
22333    fn main() {
22334        if 1 == 2 {
22335            let a = 1;
22336        }
22337    }"
22338        .unindent(),
22339        cx,
22340    )
22341    .await;
22342
22343    cx.update_editor(|editor, window, cx| {
22344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22345            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22346        });
22347    });
22348    cx.run_until_parked();
22349
22350    assert_indent_guides(
22351        0..4,
22352        vec![
22353            indent_guide(buffer_id, 1, 3, 0),
22354            indent_guide(buffer_id, 2, 2, 1),
22355        ],
22356        Some(vec![1]),
22357        &mut cx,
22358    );
22359
22360    cx.update_editor(|editor, window, cx| {
22361        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22362            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22363        });
22364    });
22365    cx.run_until_parked();
22366
22367    assert_indent_guides(
22368        0..4,
22369        vec![
22370            indent_guide(buffer_id, 1, 3, 0),
22371            indent_guide(buffer_id, 2, 2, 1),
22372        ],
22373        Some(vec![1]),
22374        &mut cx,
22375    );
22376
22377    cx.update_editor(|editor, window, cx| {
22378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22379            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22380        });
22381    });
22382    cx.run_until_parked();
22383
22384    assert_indent_guides(
22385        0..4,
22386        vec![
22387            indent_guide(buffer_id, 1, 3, 0),
22388            indent_guide(buffer_id, 2, 2, 1),
22389        ],
22390        Some(vec![0]),
22391        &mut cx,
22392    );
22393}
22394
22395#[gpui::test]
22396async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22397    let (buffer_id, mut cx) = setup_indent_guides_editor(
22398        &"
22399    fn main() {
22400        let a = 1;
22401
22402        let b = 2;
22403    }"
22404        .unindent(),
22405        cx,
22406    )
22407    .await;
22408
22409    cx.update_editor(|editor, window, cx| {
22410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22411            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22412        });
22413    });
22414
22415    assert_indent_guides(
22416        0..5,
22417        vec![indent_guide(buffer_id, 1, 3, 0)],
22418        Some(vec![0]),
22419        &mut cx,
22420    );
22421}
22422
22423#[gpui::test]
22424async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22425    let (buffer_id, mut cx) = setup_indent_guides_editor(
22426        &"
22427    def m:
22428        a = 1
22429        pass"
22430            .unindent(),
22431        cx,
22432    )
22433    .await;
22434
22435    cx.update_editor(|editor, window, cx| {
22436        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22437            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22438        });
22439    });
22440
22441    assert_indent_guides(
22442        0..3,
22443        vec![indent_guide(buffer_id, 1, 2, 0)],
22444        Some(vec![0]),
22445        &mut cx,
22446    );
22447}
22448
22449#[gpui::test]
22450async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22451    init_test(cx, |_| {});
22452    let mut cx = EditorTestContext::new(cx).await;
22453    let text = indoc! {
22454        "
22455        impl A {
22456            fn b() {
22457                0;
22458                3;
22459                5;
22460                6;
22461                7;
22462            }
22463        }
22464        "
22465    };
22466    let base_text = indoc! {
22467        "
22468        impl A {
22469            fn b() {
22470                0;
22471                1;
22472                2;
22473                3;
22474                4;
22475            }
22476            fn c() {
22477                5;
22478                6;
22479                7;
22480            }
22481        }
22482        "
22483    };
22484
22485    cx.update_editor(|editor, window, cx| {
22486        editor.set_text(text, window, cx);
22487
22488        editor.buffer().update(cx, |multibuffer, cx| {
22489            let buffer = multibuffer.as_singleton().unwrap();
22490            let diff = cx.new(|cx| {
22491                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22492            });
22493
22494            multibuffer.set_all_diff_hunks_expanded(cx);
22495            multibuffer.add_diff(diff, cx);
22496
22497            buffer.read(cx).remote_id()
22498        })
22499    });
22500    cx.run_until_parked();
22501
22502    cx.assert_state_with_diff(
22503        indoc! { "
22504          impl A {
22505              fn b() {
22506                  0;
22507        -         1;
22508        -         2;
22509                  3;
22510        -         4;
22511        -     }
22512        -     fn c() {
22513                  5;
22514                  6;
22515                  7;
22516              }
22517          }
22518          ˇ"
22519        }
22520        .to_string(),
22521    );
22522
22523    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22524        editor
22525            .snapshot(window, cx)
22526            .buffer_snapshot()
22527            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22528            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22529            .collect::<Vec<_>>()
22530    });
22531    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22532    assert_eq!(
22533        actual_guides,
22534        vec![
22535            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22536            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22537            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22538        ]
22539    );
22540}
22541
22542#[gpui::test]
22543async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22544    init_test(cx, |_| {});
22545    let mut cx = EditorTestContext::new(cx).await;
22546
22547    let diff_base = r#"
22548        a
22549        b
22550        c
22551        "#
22552    .unindent();
22553
22554    cx.set_state(
22555        &r#"
22556        ˇA
22557        b
22558        C
22559        "#
22560        .unindent(),
22561    );
22562    cx.set_head_text(&diff_base);
22563    cx.update_editor(|editor, window, cx| {
22564        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22565    });
22566    executor.run_until_parked();
22567
22568    let both_hunks_expanded = r#"
22569        - a
22570        + ˇA
22571          b
22572        - c
22573        + C
22574        "#
22575    .unindent();
22576
22577    cx.assert_state_with_diff(both_hunks_expanded.clone());
22578
22579    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22580        let snapshot = editor.snapshot(window, cx);
22581        let hunks = editor
22582            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22583            .collect::<Vec<_>>();
22584        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22585        hunks
22586            .into_iter()
22587            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22588            .collect::<Vec<_>>()
22589    });
22590    assert_eq!(hunk_ranges.len(), 2);
22591
22592    cx.update_editor(|editor, _, cx| {
22593        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22594    });
22595    executor.run_until_parked();
22596
22597    let second_hunk_expanded = r#"
22598          ˇA
22599          b
22600        - c
22601        + C
22602        "#
22603    .unindent();
22604
22605    cx.assert_state_with_diff(second_hunk_expanded);
22606
22607    cx.update_editor(|editor, _, cx| {
22608        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22609    });
22610    executor.run_until_parked();
22611
22612    cx.assert_state_with_diff(both_hunks_expanded.clone());
22613
22614    cx.update_editor(|editor, _, cx| {
22615        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22616    });
22617    executor.run_until_parked();
22618
22619    let first_hunk_expanded = r#"
22620        - a
22621        + ˇA
22622          b
22623          C
22624        "#
22625    .unindent();
22626
22627    cx.assert_state_with_diff(first_hunk_expanded);
22628
22629    cx.update_editor(|editor, _, cx| {
22630        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22631    });
22632    executor.run_until_parked();
22633
22634    cx.assert_state_with_diff(both_hunks_expanded);
22635
22636    cx.set_state(
22637        &r#"
22638        ˇA
22639        b
22640        "#
22641        .unindent(),
22642    );
22643    cx.run_until_parked();
22644
22645    // TODO this cursor position seems bad
22646    cx.assert_state_with_diff(
22647        r#"
22648        - ˇa
22649        + A
22650          b
22651        "#
22652        .unindent(),
22653    );
22654
22655    cx.update_editor(|editor, window, cx| {
22656        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22657    });
22658
22659    cx.assert_state_with_diff(
22660        r#"
22661            - ˇa
22662            + A
22663              b
22664            - c
22665            "#
22666        .unindent(),
22667    );
22668
22669    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22670        let snapshot = editor.snapshot(window, cx);
22671        let hunks = editor
22672            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22673            .collect::<Vec<_>>();
22674        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22675        hunks
22676            .into_iter()
22677            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22678            .collect::<Vec<_>>()
22679    });
22680    assert_eq!(hunk_ranges.len(), 2);
22681
22682    cx.update_editor(|editor, _, cx| {
22683        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22684    });
22685    executor.run_until_parked();
22686
22687    cx.assert_state_with_diff(
22688        r#"
22689        - ˇa
22690        + A
22691          b
22692        "#
22693        .unindent(),
22694    );
22695}
22696
22697#[gpui::test]
22698async fn test_toggle_deletion_hunk_at_start_of_file(
22699    executor: BackgroundExecutor,
22700    cx: &mut TestAppContext,
22701) {
22702    init_test(cx, |_| {});
22703    let mut cx = EditorTestContext::new(cx).await;
22704
22705    let diff_base = r#"
22706        a
22707        b
22708        c
22709        "#
22710    .unindent();
22711
22712    cx.set_state(
22713        &r#"
22714        ˇb
22715        c
22716        "#
22717        .unindent(),
22718    );
22719    cx.set_head_text(&diff_base);
22720    cx.update_editor(|editor, window, cx| {
22721        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22722    });
22723    executor.run_until_parked();
22724
22725    let hunk_expanded = r#"
22726        - a
22727          ˇb
22728          c
22729        "#
22730    .unindent();
22731
22732    cx.assert_state_with_diff(hunk_expanded.clone());
22733
22734    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22735        let snapshot = editor.snapshot(window, cx);
22736        let hunks = editor
22737            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22738            .collect::<Vec<_>>();
22739        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22740        hunks
22741            .into_iter()
22742            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22743            .collect::<Vec<_>>()
22744    });
22745    assert_eq!(hunk_ranges.len(), 1);
22746
22747    cx.update_editor(|editor, _, cx| {
22748        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22749    });
22750    executor.run_until_parked();
22751
22752    let hunk_collapsed = r#"
22753          ˇb
22754          c
22755        "#
22756    .unindent();
22757
22758    cx.assert_state_with_diff(hunk_collapsed);
22759
22760    cx.update_editor(|editor, _, cx| {
22761        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22762    });
22763    executor.run_until_parked();
22764
22765    cx.assert_state_with_diff(hunk_expanded);
22766}
22767
22768#[gpui::test]
22769async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22770    executor: BackgroundExecutor,
22771    cx: &mut TestAppContext,
22772) {
22773    init_test(cx, |_| {});
22774    let mut cx = EditorTestContext::new(cx).await;
22775
22776    cx.set_state("ˇnew\nsecond\nthird\n");
22777    cx.set_head_text("old\nsecond\nthird\n");
22778    cx.update_editor(|editor, window, cx| {
22779        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22780    });
22781    executor.run_until_parked();
22782    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22783
22784    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22785    cx.update_editor(|editor, window, cx| {
22786        let snapshot = editor.snapshot(window, cx);
22787        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22788        let hunks = editor
22789            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22790            .collect::<Vec<_>>();
22791        assert_eq!(hunks.len(), 1);
22792        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22793        editor.toggle_single_diff_hunk(hunk_range, cx)
22794    });
22795    executor.run_until_parked();
22796    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22797
22798    // Keep the editor scrolled to the top so the full hunk remains visible.
22799    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22800}
22801
22802#[gpui::test]
22803async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22804    init_test(cx, |_| {});
22805
22806    let fs = FakeFs::new(cx.executor());
22807    fs.insert_tree(
22808        path!("/test"),
22809        json!({
22810            ".git": {},
22811            "file-1": "ONE\n",
22812            "file-2": "TWO\n",
22813            "file-3": "THREE\n",
22814        }),
22815    )
22816    .await;
22817
22818    fs.set_head_for_repo(
22819        path!("/test/.git").as_ref(),
22820        &[
22821            ("file-1", "one\n".into()),
22822            ("file-2", "two\n".into()),
22823            ("file-3", "three\n".into()),
22824        ],
22825        "deadbeef",
22826    );
22827
22828    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22829    let mut buffers = vec![];
22830    for i in 1..=3 {
22831        let buffer = project
22832            .update(cx, |project, cx| {
22833                let path = format!(path!("/test/file-{}"), i);
22834                project.open_local_buffer(path, cx)
22835            })
22836            .await
22837            .unwrap();
22838        buffers.push(buffer);
22839    }
22840
22841    let multibuffer = cx.new(|cx| {
22842        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22843        multibuffer.set_all_diff_hunks_expanded(cx);
22844        for buffer in &buffers {
22845            let snapshot = buffer.read(cx).snapshot();
22846            multibuffer.set_excerpts_for_path(
22847                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22848                buffer.clone(),
22849                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22850                2,
22851                cx,
22852            );
22853        }
22854        multibuffer
22855    });
22856
22857    let editor = cx.add_window(|window, cx| {
22858        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22859    });
22860    cx.run_until_parked();
22861
22862    let snapshot = editor
22863        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22864        .unwrap();
22865    let hunks = snapshot
22866        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22867        .map(|hunk| match hunk {
22868            DisplayDiffHunk::Unfolded {
22869                display_row_range, ..
22870            } => display_row_range,
22871            DisplayDiffHunk::Folded { .. } => unreachable!(),
22872        })
22873        .collect::<Vec<_>>();
22874    assert_eq!(
22875        hunks,
22876        [
22877            DisplayRow(2)..DisplayRow(4),
22878            DisplayRow(7)..DisplayRow(9),
22879            DisplayRow(12)..DisplayRow(14),
22880        ]
22881    );
22882}
22883
22884#[gpui::test]
22885async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22886    init_test(cx, |_| {});
22887
22888    let mut cx = EditorTestContext::new(cx).await;
22889    cx.set_head_text(indoc! { "
22890        one
22891        two
22892        three
22893        four
22894        five
22895        "
22896    });
22897    cx.set_index_text(indoc! { "
22898        one
22899        two
22900        three
22901        four
22902        five
22903        "
22904    });
22905    cx.set_state(indoc! {"
22906        one
22907        TWO
22908        ˇTHREE
22909        FOUR
22910        five
22911    "});
22912    cx.run_until_parked();
22913    cx.update_editor(|editor, window, cx| {
22914        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22915    });
22916    cx.run_until_parked();
22917    cx.assert_index_text(Some(indoc! {"
22918        one
22919        TWO
22920        THREE
22921        FOUR
22922        five
22923    "}));
22924    cx.set_state(indoc! { "
22925        one
22926        TWO
22927        ˇTHREE-HUNDRED
22928        FOUR
22929        five
22930    "});
22931    cx.run_until_parked();
22932    cx.update_editor(|editor, window, cx| {
22933        let snapshot = editor.snapshot(window, cx);
22934        let hunks = editor
22935            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22936            .collect::<Vec<_>>();
22937        assert_eq!(hunks.len(), 1);
22938        assert_eq!(
22939            hunks[0].status(),
22940            DiffHunkStatus {
22941                kind: DiffHunkStatusKind::Modified,
22942                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22943            }
22944        );
22945
22946        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22947    });
22948    cx.run_until_parked();
22949    cx.assert_index_text(Some(indoc! {"
22950        one
22951        TWO
22952        THREE-HUNDRED
22953        FOUR
22954        five
22955    "}));
22956}
22957
22958#[gpui::test]
22959fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22960    init_test(cx, |_| {});
22961
22962    let editor = cx.add_window(|window, cx| {
22963        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22964        build_editor(buffer, window, cx)
22965    });
22966
22967    let render_args = Arc::new(Mutex::new(None));
22968    let snapshot = editor
22969        .update(cx, |editor, window, cx| {
22970            let snapshot = editor.buffer().read(cx).snapshot(cx);
22971            let range =
22972                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22973
22974            struct RenderArgs {
22975                row: MultiBufferRow,
22976                folded: bool,
22977                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22978            }
22979
22980            let crease = Crease::inline(
22981                range,
22982                FoldPlaceholder::test(),
22983                {
22984                    let toggle_callback = render_args.clone();
22985                    move |row, folded, callback, _window, _cx| {
22986                        *toggle_callback.lock() = Some(RenderArgs {
22987                            row,
22988                            folded,
22989                            callback,
22990                        });
22991                        div()
22992                    }
22993                },
22994                |_row, _folded, _window, _cx| div(),
22995            );
22996
22997            editor.insert_creases(Some(crease), cx);
22998            let snapshot = editor.snapshot(window, cx);
22999            let _div =
23000                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
23001            snapshot
23002        })
23003        .unwrap();
23004
23005    let render_args = render_args.lock().take().unwrap();
23006    assert_eq!(render_args.row, MultiBufferRow(1));
23007    assert!(!render_args.folded);
23008    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23009
23010    cx.update_window(*editor, |_, window, cx| {
23011        (render_args.callback)(true, window, cx)
23012    })
23013    .unwrap();
23014    let snapshot = editor
23015        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23016        .unwrap();
23017    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
23018
23019    cx.update_window(*editor, |_, window, cx| {
23020        (render_args.callback)(false, window, cx)
23021    })
23022    .unwrap();
23023    let snapshot = editor
23024        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23025        .unwrap();
23026    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23027}
23028
23029#[gpui::test]
23030async fn test_input_text(cx: &mut TestAppContext) {
23031    init_test(cx, |_| {});
23032    let mut cx = EditorTestContext::new(cx).await;
23033
23034    cx.set_state(
23035        &r#"ˇone
23036        two
23037
23038        three
23039        fourˇ
23040        five
23041
23042        siˇx"#
23043            .unindent(),
23044    );
23045
23046    cx.dispatch_action(HandleInput(String::new()));
23047    cx.assert_editor_state(
23048        &r#"ˇone
23049        two
23050
23051        three
23052        fourˇ
23053        five
23054
23055        siˇx"#
23056            .unindent(),
23057    );
23058
23059    cx.dispatch_action(HandleInput("AAAA".to_string()));
23060    cx.assert_editor_state(
23061        &r#"AAAAˇone
23062        two
23063
23064        three
23065        fourAAAAˇ
23066        five
23067
23068        siAAAAˇx"#
23069            .unindent(),
23070    );
23071}
23072
23073#[gpui::test]
23074async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
23075    init_test(cx, |_| {});
23076
23077    let mut cx = EditorTestContext::new(cx).await;
23078    cx.set_state(
23079        r#"let foo = 1;
23080let foo = 2;
23081let foo = 3;
23082let fooˇ = 4;
23083let foo = 5;
23084let foo = 6;
23085let foo = 7;
23086let foo = 8;
23087let foo = 9;
23088let foo = 10;
23089let foo = 11;
23090let foo = 12;
23091let foo = 13;
23092let foo = 14;
23093let foo = 15;"#,
23094    );
23095
23096    cx.update_editor(|e, window, cx| {
23097        assert_eq!(
23098            e.next_scroll_position,
23099            NextScrollCursorCenterTopBottom::Center,
23100            "Default next scroll direction is center",
23101        );
23102
23103        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23104        assert_eq!(
23105            e.next_scroll_position,
23106            NextScrollCursorCenterTopBottom::Top,
23107            "After center, next scroll direction should be top",
23108        );
23109
23110        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23111        assert_eq!(
23112            e.next_scroll_position,
23113            NextScrollCursorCenterTopBottom::Bottom,
23114            "After top, next scroll direction should be bottom",
23115        );
23116
23117        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23118        assert_eq!(
23119            e.next_scroll_position,
23120            NextScrollCursorCenterTopBottom::Center,
23121            "After bottom, scrolling should start over",
23122        );
23123
23124        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23125        assert_eq!(
23126            e.next_scroll_position,
23127            NextScrollCursorCenterTopBottom::Top,
23128            "Scrolling continues if retriggered fast enough"
23129        );
23130    });
23131
23132    cx.executor()
23133        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
23134    cx.executor().run_until_parked();
23135    cx.update_editor(|e, _, _| {
23136        assert_eq!(
23137            e.next_scroll_position,
23138            NextScrollCursorCenterTopBottom::Center,
23139            "If scrolling is not triggered fast enough, it should reset"
23140        );
23141    });
23142}
23143
23144#[gpui::test]
23145async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
23146    init_test(cx, |_| {});
23147    let mut cx = EditorLspTestContext::new_rust(
23148        lsp::ServerCapabilities {
23149            definition_provider: Some(lsp::OneOf::Left(true)),
23150            references_provider: Some(lsp::OneOf::Left(true)),
23151            ..lsp::ServerCapabilities::default()
23152        },
23153        cx,
23154    )
23155    .await;
23156
23157    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
23158        let go_to_definition = cx
23159            .lsp
23160            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23161                move |params, _| async move {
23162                    if empty_go_to_definition {
23163                        Ok(None)
23164                    } else {
23165                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
23166                            uri: params.text_document_position_params.text_document.uri,
23167                            range: lsp::Range::new(
23168                                lsp::Position::new(4, 3),
23169                                lsp::Position::new(4, 6),
23170                            ),
23171                        })))
23172                    }
23173                },
23174            );
23175        let references = cx
23176            .lsp
23177            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23178                Ok(Some(vec![lsp::Location {
23179                    uri: params.text_document_position.text_document.uri,
23180                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
23181                }]))
23182            });
23183        (go_to_definition, references)
23184    };
23185
23186    cx.set_state(
23187        &r#"fn one() {
23188            let mut a = ˇtwo();
23189        }
23190
23191        fn two() {}"#
23192            .unindent(),
23193    );
23194    set_up_lsp_handlers(false, &mut cx);
23195    let navigated = cx
23196        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23197        .await
23198        .expect("Failed to navigate to definition");
23199    assert_eq!(
23200        navigated,
23201        Navigated::Yes,
23202        "Should have navigated to definition from the GetDefinition response"
23203    );
23204    cx.assert_editor_state(
23205        &r#"fn one() {
23206            let mut a = two();
23207        }
23208
23209        fn «twoˇ»() {}"#
23210            .unindent(),
23211    );
23212
23213    let editors = cx.update_workspace(|workspace, _, cx| {
23214        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23215    });
23216    cx.update_editor(|_, _, test_editor_cx| {
23217        assert_eq!(
23218            editors.len(),
23219            1,
23220            "Initially, only one, test, editor should be open in the workspace"
23221        );
23222        assert_eq!(
23223            test_editor_cx.entity(),
23224            editors.last().expect("Asserted len is 1").clone()
23225        );
23226    });
23227
23228    set_up_lsp_handlers(true, &mut cx);
23229    let navigated = cx
23230        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23231        .await
23232        .expect("Failed to navigate to lookup references");
23233    assert_eq!(
23234        navigated,
23235        Navigated::Yes,
23236        "Should have navigated to references as a fallback after empty GoToDefinition response"
23237    );
23238    // We should not change the selections in the existing file,
23239    // if opening another milti buffer with the references
23240    cx.assert_editor_state(
23241        &r#"fn one() {
23242            let mut a = two();
23243        }
23244
23245        fn «twoˇ»() {}"#
23246            .unindent(),
23247    );
23248    let editors = cx.update_workspace(|workspace, _, cx| {
23249        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23250    });
23251    cx.update_editor(|_, _, test_editor_cx| {
23252        assert_eq!(
23253            editors.len(),
23254            2,
23255            "After falling back to references search, we open a new editor with the results"
23256        );
23257        let references_fallback_text = editors
23258            .into_iter()
23259            .find(|new_editor| *new_editor != test_editor_cx.entity())
23260            .expect("Should have one non-test editor now")
23261            .read(test_editor_cx)
23262            .text(test_editor_cx);
23263        assert_eq!(
23264            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
23265            "Should use the range from the references response and not the GoToDefinition one"
23266        );
23267    });
23268}
23269
23270#[gpui::test]
23271async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
23272    init_test(cx, |_| {});
23273    cx.update(|cx| {
23274        let mut editor_settings = EditorSettings::get_global(cx).clone();
23275        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
23276        EditorSettings::override_global(editor_settings, cx);
23277    });
23278    let mut cx = EditorLspTestContext::new_rust(
23279        lsp::ServerCapabilities {
23280            definition_provider: Some(lsp::OneOf::Left(true)),
23281            references_provider: Some(lsp::OneOf::Left(true)),
23282            ..lsp::ServerCapabilities::default()
23283        },
23284        cx,
23285    )
23286    .await;
23287    let original_state = r#"fn one() {
23288        let mut a = ˇtwo();
23289    }
23290
23291    fn two() {}"#
23292        .unindent();
23293    cx.set_state(&original_state);
23294
23295    let mut go_to_definition = cx
23296        .lsp
23297        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23298            move |_, _| async move { Ok(None) },
23299        );
23300    let _references = cx
23301        .lsp
23302        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23303            panic!("Should not call for references with no go to definition fallback")
23304        });
23305
23306    let navigated = cx
23307        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23308        .await
23309        .expect("Failed to navigate to lookup references");
23310    go_to_definition
23311        .next()
23312        .await
23313        .expect("Should have called the go_to_definition handler");
23314
23315    assert_eq!(
23316        navigated,
23317        Navigated::No,
23318        "Should have navigated to references as a fallback after empty GoToDefinition response"
23319    );
23320    cx.assert_editor_state(&original_state);
23321    let editors = cx.update_workspace(|workspace, _, cx| {
23322        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23323    });
23324    cx.update_editor(|_, _, _| {
23325        assert_eq!(
23326            editors.len(),
23327            1,
23328            "After unsuccessful fallback, no other editor should have been opened"
23329        );
23330    });
23331}
23332
23333#[gpui::test]
23334async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23335    init_test(cx, |_| {});
23336    let mut cx = EditorLspTestContext::new_rust(
23337        lsp::ServerCapabilities {
23338            references_provider: Some(lsp::OneOf::Left(true)),
23339            ..lsp::ServerCapabilities::default()
23340        },
23341        cx,
23342    )
23343    .await;
23344
23345    cx.set_state(
23346        &r#"
23347        fn one() {
23348            let mut a = two();
23349        }
23350
23351        fn ˇtwo() {}"#
23352            .unindent(),
23353    );
23354    cx.lsp
23355        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23356            Ok(Some(vec![
23357                lsp::Location {
23358                    uri: params.text_document_position.text_document.uri.clone(),
23359                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23360                },
23361                lsp::Location {
23362                    uri: params.text_document_position.text_document.uri,
23363                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23364                },
23365            ]))
23366        });
23367    let navigated = cx
23368        .update_editor(|editor, window, cx| {
23369            editor.find_all_references(&FindAllReferences::default(), window, cx)
23370        })
23371        .unwrap()
23372        .await
23373        .expect("Failed to navigate to references");
23374    assert_eq!(
23375        navigated,
23376        Navigated::Yes,
23377        "Should have navigated to references from the FindAllReferences response"
23378    );
23379    cx.assert_editor_state(
23380        &r#"fn one() {
23381            let mut a = two();
23382        }
23383
23384        fn ˇtwo() {}"#
23385            .unindent(),
23386    );
23387
23388    let editors = cx.update_workspace(|workspace, _, cx| {
23389        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23390    });
23391    cx.update_editor(|_, _, _| {
23392        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23393    });
23394
23395    cx.set_state(
23396        &r#"fn one() {
23397            let mut a = ˇtwo();
23398        }
23399
23400        fn two() {}"#
23401            .unindent(),
23402    );
23403    let navigated = cx
23404        .update_editor(|editor, window, cx| {
23405            editor.find_all_references(&FindAllReferences::default(), window, cx)
23406        })
23407        .unwrap()
23408        .await
23409        .expect("Failed to navigate to references");
23410    assert_eq!(
23411        navigated,
23412        Navigated::Yes,
23413        "Should have navigated to references from the FindAllReferences response"
23414    );
23415    cx.assert_editor_state(
23416        &r#"fn one() {
23417            let mut a = ˇtwo();
23418        }
23419
23420        fn two() {}"#
23421            .unindent(),
23422    );
23423    let editors = cx.update_workspace(|workspace, _, cx| {
23424        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23425    });
23426    cx.update_editor(|_, _, _| {
23427        assert_eq!(
23428            editors.len(),
23429            2,
23430            "should have re-used the previous multibuffer"
23431        );
23432    });
23433
23434    cx.set_state(
23435        &r#"fn one() {
23436            let mut a = ˇtwo();
23437        }
23438        fn three() {}
23439        fn two() {}"#
23440            .unindent(),
23441    );
23442    cx.lsp
23443        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23444            Ok(Some(vec![
23445                lsp::Location {
23446                    uri: params.text_document_position.text_document.uri.clone(),
23447                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23448                },
23449                lsp::Location {
23450                    uri: params.text_document_position.text_document.uri,
23451                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23452                },
23453            ]))
23454        });
23455    let navigated = cx
23456        .update_editor(|editor, window, cx| {
23457            editor.find_all_references(&FindAllReferences::default(), window, cx)
23458        })
23459        .unwrap()
23460        .await
23461        .expect("Failed to navigate to references");
23462    assert_eq!(
23463        navigated,
23464        Navigated::Yes,
23465        "Should have navigated to references from the FindAllReferences response"
23466    );
23467    cx.assert_editor_state(
23468        &r#"fn one() {
23469                let mut a = ˇtwo();
23470            }
23471            fn three() {}
23472            fn two() {}"#
23473            .unindent(),
23474    );
23475    let editors = cx.update_workspace(|workspace, _, cx| {
23476        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23477    });
23478    cx.update_editor(|_, _, _| {
23479        assert_eq!(
23480            editors.len(),
23481            3,
23482            "should have used a new multibuffer as offsets changed"
23483        );
23484    });
23485}
23486#[gpui::test]
23487async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23488    init_test(cx, |_| {});
23489
23490    let language = Arc::new(Language::new(
23491        LanguageConfig::default(),
23492        Some(tree_sitter_rust::LANGUAGE.into()),
23493    ));
23494
23495    let text = r#"
23496        #[cfg(test)]
23497        mod tests() {
23498            #[test]
23499            fn runnable_1() {
23500                let a = 1;
23501            }
23502
23503            #[test]
23504            fn runnable_2() {
23505                let a = 1;
23506                let b = 2;
23507            }
23508        }
23509    "#
23510    .unindent();
23511
23512    let fs = FakeFs::new(cx.executor());
23513    fs.insert_file("/file.rs", Default::default()).await;
23514
23515    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23516    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23517    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23518    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23519    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23520
23521    let editor = cx.new_window_entity(|window, cx| {
23522        Editor::new(
23523            EditorMode::full(),
23524            multi_buffer,
23525            Some(project.clone()),
23526            window,
23527            cx,
23528        )
23529    });
23530
23531    editor.update_in(cx, |editor, window, cx| {
23532        let snapshot = editor.buffer().read(cx).snapshot(cx);
23533        editor.tasks.insert(
23534            (buffer.read(cx).remote_id(), 3),
23535            RunnableTasks {
23536                templates: vec![],
23537                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23538                column: 0,
23539                extra_variables: HashMap::default(),
23540                context_range: BufferOffset(43)..BufferOffset(85),
23541            },
23542        );
23543        editor.tasks.insert(
23544            (buffer.read(cx).remote_id(), 8),
23545            RunnableTasks {
23546                templates: vec![],
23547                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23548                column: 0,
23549                extra_variables: HashMap::default(),
23550                context_range: BufferOffset(86)..BufferOffset(191),
23551            },
23552        );
23553
23554        // Test finding task when cursor is inside function body
23555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23556            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23557        });
23558        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23559        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23560
23561        // Test finding task when cursor is on function name
23562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23563            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23564        });
23565        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23566        assert_eq!(row, 8, "Should find task when cursor is on function name");
23567    });
23568}
23569
23570#[gpui::test]
23571async fn test_folding_buffers(cx: &mut TestAppContext) {
23572    init_test(cx, |_| {});
23573
23574    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23575    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23576    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23577
23578    let fs = FakeFs::new(cx.executor());
23579    fs.insert_tree(
23580        path!("/a"),
23581        json!({
23582            "first.rs": sample_text_1,
23583            "second.rs": sample_text_2,
23584            "third.rs": sample_text_3,
23585        }),
23586    )
23587    .await;
23588    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23589    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23590    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23591    let worktree = project.update(cx, |project, cx| {
23592        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23593        assert_eq!(worktrees.len(), 1);
23594        worktrees.pop().unwrap()
23595    });
23596    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23597
23598    let buffer_1 = project
23599        .update(cx, |project, cx| {
23600            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23601        })
23602        .await
23603        .unwrap();
23604    let buffer_2 = project
23605        .update(cx, |project, cx| {
23606            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23607        })
23608        .await
23609        .unwrap();
23610    let buffer_3 = project
23611        .update(cx, |project, cx| {
23612            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23613        })
23614        .await
23615        .unwrap();
23616
23617    let multi_buffer = cx.new(|cx| {
23618        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23619        multi_buffer.push_excerpts(
23620            buffer_1.clone(),
23621            [
23622                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23623                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23624                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23625            ],
23626            cx,
23627        );
23628        multi_buffer.push_excerpts(
23629            buffer_2.clone(),
23630            [
23631                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23632                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23633                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23634            ],
23635            cx,
23636        );
23637        multi_buffer.push_excerpts(
23638            buffer_3.clone(),
23639            [
23640                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23641                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23642                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23643            ],
23644            cx,
23645        );
23646        multi_buffer
23647    });
23648    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23649        Editor::new(
23650            EditorMode::full(),
23651            multi_buffer.clone(),
23652            Some(project.clone()),
23653            window,
23654            cx,
23655        )
23656    });
23657
23658    assert_eq!(
23659        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23660        "\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",
23661    );
23662
23663    multi_buffer_editor.update(cx, |editor, cx| {
23664        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23665    });
23666    assert_eq!(
23667        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23668        "\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",
23669        "After folding the first buffer, its text should not be displayed"
23670    );
23671
23672    multi_buffer_editor.update(cx, |editor, cx| {
23673        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23674    });
23675    assert_eq!(
23676        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23677        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23678        "After folding the second buffer, its text should not be displayed"
23679    );
23680
23681    multi_buffer_editor.update(cx, |editor, cx| {
23682        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23683    });
23684    assert_eq!(
23685        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23686        "\n\n\n\n\n",
23687        "After folding the third buffer, its text should not be displayed"
23688    );
23689
23690    // Emulate selection inside the fold logic, that should work
23691    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23692        editor
23693            .snapshot(window, cx)
23694            .next_line_boundary(Point::new(0, 4));
23695    });
23696
23697    multi_buffer_editor.update(cx, |editor, cx| {
23698        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23699    });
23700    assert_eq!(
23701        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23702        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23703        "After unfolding the second buffer, its text should be displayed"
23704    );
23705
23706    // Typing inside of buffer 1 causes that buffer to be unfolded.
23707    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23708        assert_eq!(
23709            multi_buffer
23710                .read(cx)
23711                .snapshot(cx)
23712                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23713                .collect::<String>(),
23714            "bbbb"
23715        );
23716        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23717            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23718        });
23719        editor.handle_input("B", window, cx);
23720    });
23721
23722    assert_eq!(
23723        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23724        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23725        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23726    );
23727
23728    multi_buffer_editor.update(cx, |editor, cx| {
23729        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23730    });
23731    assert_eq!(
23732        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23733        "\n\naaaa\nBbbbb\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",
23734        "After unfolding the all buffers, all original text should be displayed"
23735    );
23736}
23737
23738#[gpui::test]
23739async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23740    init_test(cx, |_| {});
23741
23742    let sample_text_1 = "1111\n2222\n3333".to_string();
23743    let sample_text_2 = "4444\n5555\n6666".to_string();
23744    let sample_text_3 = "7777\n8888\n9999".to_string();
23745
23746    let fs = FakeFs::new(cx.executor());
23747    fs.insert_tree(
23748        path!("/a"),
23749        json!({
23750            "first.rs": sample_text_1,
23751            "second.rs": sample_text_2,
23752            "third.rs": sample_text_3,
23753        }),
23754    )
23755    .await;
23756    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23758    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23759    let worktree = project.update(cx, |project, cx| {
23760        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23761        assert_eq!(worktrees.len(), 1);
23762        worktrees.pop().unwrap()
23763    });
23764    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23765
23766    let buffer_1 = project
23767        .update(cx, |project, cx| {
23768            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23769        })
23770        .await
23771        .unwrap();
23772    let buffer_2 = project
23773        .update(cx, |project, cx| {
23774            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23775        })
23776        .await
23777        .unwrap();
23778    let buffer_3 = project
23779        .update(cx, |project, cx| {
23780            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23781        })
23782        .await
23783        .unwrap();
23784
23785    let multi_buffer = cx.new(|cx| {
23786        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23787        multi_buffer.push_excerpts(
23788            buffer_1.clone(),
23789            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23790            cx,
23791        );
23792        multi_buffer.push_excerpts(
23793            buffer_2.clone(),
23794            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23795            cx,
23796        );
23797        multi_buffer.push_excerpts(
23798            buffer_3.clone(),
23799            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23800            cx,
23801        );
23802        multi_buffer
23803    });
23804
23805    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23806        Editor::new(
23807            EditorMode::full(),
23808            multi_buffer,
23809            Some(project.clone()),
23810            window,
23811            cx,
23812        )
23813    });
23814
23815    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23816    assert_eq!(
23817        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23818        full_text,
23819    );
23820
23821    multi_buffer_editor.update(cx, |editor, cx| {
23822        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23823    });
23824    assert_eq!(
23825        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23826        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23827        "After folding the first buffer, its text should not be displayed"
23828    );
23829
23830    multi_buffer_editor.update(cx, |editor, cx| {
23831        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23832    });
23833
23834    assert_eq!(
23835        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23836        "\n\n\n\n\n\n7777\n8888\n9999",
23837        "After folding the second buffer, its text should not be displayed"
23838    );
23839
23840    multi_buffer_editor.update(cx, |editor, cx| {
23841        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23842    });
23843    assert_eq!(
23844        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23845        "\n\n\n\n\n",
23846        "After folding the third buffer, its text should not be displayed"
23847    );
23848
23849    multi_buffer_editor.update(cx, |editor, cx| {
23850        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23851    });
23852    assert_eq!(
23853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23854        "\n\n\n\n4444\n5555\n6666\n\n",
23855        "After unfolding the second buffer, its text should be displayed"
23856    );
23857
23858    multi_buffer_editor.update(cx, |editor, cx| {
23859        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23860    });
23861    assert_eq!(
23862        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23863        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23864        "After unfolding the first buffer, its text should be displayed"
23865    );
23866
23867    multi_buffer_editor.update(cx, |editor, cx| {
23868        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23869    });
23870    assert_eq!(
23871        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23872        full_text,
23873        "After unfolding all buffers, all original text should be displayed"
23874    );
23875}
23876
23877#[gpui::test]
23878async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23879    init_test(cx, |_| {});
23880
23881    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23882
23883    let fs = FakeFs::new(cx.executor());
23884    fs.insert_tree(
23885        path!("/a"),
23886        json!({
23887            "main.rs": sample_text,
23888        }),
23889    )
23890    .await;
23891    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23892    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23893    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23894    let worktree = project.update(cx, |project, cx| {
23895        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23896        assert_eq!(worktrees.len(), 1);
23897        worktrees.pop().unwrap()
23898    });
23899    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23900
23901    let buffer_1 = project
23902        .update(cx, |project, cx| {
23903            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23904        })
23905        .await
23906        .unwrap();
23907
23908    let multi_buffer = cx.new(|cx| {
23909        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23910        multi_buffer.push_excerpts(
23911            buffer_1.clone(),
23912            [ExcerptRange::new(
23913                Point::new(0, 0)
23914                    ..Point::new(
23915                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23916                        0,
23917                    ),
23918            )],
23919            cx,
23920        );
23921        multi_buffer
23922    });
23923    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23924        Editor::new(
23925            EditorMode::full(),
23926            multi_buffer,
23927            Some(project.clone()),
23928            window,
23929            cx,
23930        )
23931    });
23932
23933    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23934    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23935        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23936        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23937        editor.highlight_text(
23938            HighlightKey::Editor,
23939            vec![highlight_range.clone()],
23940            HighlightStyle::color(Hsla::green()),
23941            cx,
23942        );
23943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23944            s.select_ranges(Some(highlight_range))
23945        });
23946    });
23947
23948    let full_text = format!("\n\n{sample_text}");
23949    assert_eq!(
23950        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23951        full_text,
23952    );
23953}
23954
23955#[gpui::test]
23956async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23957    init_test(cx, |_| {});
23958    cx.update(|cx| {
23959        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23960            "keymaps/default-linux.json",
23961            cx,
23962        )
23963        .unwrap();
23964        cx.bind_keys(default_key_bindings);
23965    });
23966
23967    let (editor, cx) = cx.add_window_view(|window, cx| {
23968        let multi_buffer = MultiBuffer::build_multi(
23969            [
23970                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23971                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23972                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23973                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23974            ],
23975            cx,
23976        );
23977        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23978
23979        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23980        // fold all but the second buffer, so that we test navigating between two
23981        // adjacent folded buffers, as well as folded buffers at the start and
23982        // end the multibuffer
23983        editor.fold_buffer(buffer_ids[0], cx);
23984        editor.fold_buffer(buffer_ids[2], cx);
23985        editor.fold_buffer(buffer_ids[3], cx);
23986
23987        editor
23988    });
23989    cx.simulate_resize(size(px(1000.), px(1000.)));
23990
23991    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23992    cx.assert_excerpts_with_selections(indoc! {"
23993        [EXCERPT]
23994        ˇ[FOLDED]
23995        [EXCERPT]
23996        a1
23997        b1
23998        [EXCERPT]
23999        [FOLDED]
24000        [EXCERPT]
24001        [FOLDED]
24002        "
24003    });
24004    cx.simulate_keystroke("down");
24005    cx.assert_excerpts_with_selections(indoc! {"
24006        [EXCERPT]
24007        [FOLDED]
24008        [EXCERPT]
24009        ˇa1
24010        b1
24011        [EXCERPT]
24012        [FOLDED]
24013        [EXCERPT]
24014        [FOLDED]
24015        "
24016    });
24017    cx.simulate_keystroke("down");
24018    cx.assert_excerpts_with_selections(indoc! {"
24019        [EXCERPT]
24020        [FOLDED]
24021        [EXCERPT]
24022        a1
24023        ˇb1
24024        [EXCERPT]
24025        [FOLDED]
24026        [EXCERPT]
24027        [FOLDED]
24028        "
24029    });
24030    cx.simulate_keystroke("down");
24031    cx.assert_excerpts_with_selections(indoc! {"
24032        [EXCERPT]
24033        [FOLDED]
24034        [EXCERPT]
24035        a1
24036        b1
24037        ˇ[EXCERPT]
24038        [FOLDED]
24039        [EXCERPT]
24040        [FOLDED]
24041        "
24042    });
24043    cx.simulate_keystroke("down");
24044    cx.assert_excerpts_with_selections(indoc! {"
24045        [EXCERPT]
24046        [FOLDED]
24047        [EXCERPT]
24048        a1
24049        b1
24050        [EXCERPT]
24051        ˇ[FOLDED]
24052        [EXCERPT]
24053        [FOLDED]
24054        "
24055    });
24056    for _ in 0..5 {
24057        cx.simulate_keystroke("down");
24058        cx.assert_excerpts_with_selections(indoc! {"
24059            [EXCERPT]
24060            [FOLDED]
24061            [EXCERPT]
24062            a1
24063            b1
24064            [EXCERPT]
24065            [FOLDED]
24066            [EXCERPT]
24067            ˇ[FOLDED]
24068            "
24069        });
24070    }
24071
24072    cx.simulate_keystroke("up");
24073    cx.assert_excerpts_with_selections(indoc! {"
24074        [EXCERPT]
24075        [FOLDED]
24076        [EXCERPT]
24077        a1
24078        b1
24079        [EXCERPT]
24080        ˇ[FOLDED]
24081        [EXCERPT]
24082        [FOLDED]
24083        "
24084    });
24085    cx.simulate_keystroke("up");
24086    cx.assert_excerpts_with_selections(indoc! {"
24087        [EXCERPT]
24088        [FOLDED]
24089        [EXCERPT]
24090        a1
24091        b1
24092        ˇ[EXCERPT]
24093        [FOLDED]
24094        [EXCERPT]
24095        [FOLDED]
24096        "
24097    });
24098    cx.simulate_keystroke("up");
24099    cx.assert_excerpts_with_selections(indoc! {"
24100        [EXCERPT]
24101        [FOLDED]
24102        [EXCERPT]
24103        a1
24104        ˇb1
24105        [EXCERPT]
24106        [FOLDED]
24107        [EXCERPT]
24108        [FOLDED]
24109        "
24110    });
24111    cx.simulate_keystroke("up");
24112    cx.assert_excerpts_with_selections(indoc! {"
24113        [EXCERPT]
24114        [FOLDED]
24115        [EXCERPT]
24116        ˇa1
24117        b1
24118        [EXCERPT]
24119        [FOLDED]
24120        [EXCERPT]
24121        [FOLDED]
24122        "
24123    });
24124    for _ in 0..5 {
24125        cx.simulate_keystroke("up");
24126        cx.assert_excerpts_with_selections(indoc! {"
24127            [EXCERPT]
24128            ˇ[FOLDED]
24129            [EXCERPT]
24130            a1
24131            b1
24132            [EXCERPT]
24133            [FOLDED]
24134            [EXCERPT]
24135            [FOLDED]
24136            "
24137        });
24138    }
24139}
24140
24141#[gpui::test]
24142async fn test_edit_prediction_text(cx: &mut TestAppContext) {
24143    init_test(cx, |_| {});
24144
24145    // Simple insertion
24146    assert_highlighted_edits(
24147        "Hello, world!",
24148        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
24149        true,
24150        cx,
24151        |highlighted_edits, cx| {
24152            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
24153            assert_eq!(highlighted_edits.highlights.len(), 1);
24154            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
24155            assert_eq!(
24156                highlighted_edits.highlights[0].1.background_color,
24157                Some(cx.theme().status().created_background)
24158            );
24159        },
24160    )
24161    .await;
24162
24163    // Replacement
24164    assert_highlighted_edits(
24165        "This is a test.",
24166        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
24167        false,
24168        cx,
24169        |highlighted_edits, cx| {
24170            assert_eq!(highlighted_edits.text, "That is a test.");
24171            assert_eq!(highlighted_edits.highlights.len(), 1);
24172            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
24173            assert_eq!(
24174                highlighted_edits.highlights[0].1.background_color,
24175                Some(cx.theme().status().created_background)
24176            );
24177        },
24178    )
24179    .await;
24180
24181    // Multiple edits
24182    assert_highlighted_edits(
24183        "Hello, world!",
24184        vec![
24185            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
24186            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
24187        ],
24188        false,
24189        cx,
24190        |highlighted_edits, cx| {
24191            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
24192            assert_eq!(highlighted_edits.highlights.len(), 2);
24193            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
24194            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
24195            assert_eq!(
24196                highlighted_edits.highlights[0].1.background_color,
24197                Some(cx.theme().status().created_background)
24198            );
24199            assert_eq!(
24200                highlighted_edits.highlights[1].1.background_color,
24201                Some(cx.theme().status().created_background)
24202            );
24203        },
24204    )
24205    .await;
24206
24207    // Multiple lines with edits
24208    assert_highlighted_edits(
24209        "First line\nSecond line\nThird line\nFourth line",
24210        vec![
24211            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
24212            (
24213                Point::new(2, 0)..Point::new(2, 10),
24214                "New third line".to_string(),
24215            ),
24216            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
24217        ],
24218        false,
24219        cx,
24220        |highlighted_edits, cx| {
24221            assert_eq!(
24222                highlighted_edits.text,
24223                "Second modified\nNew third line\nFourth updated line"
24224            );
24225            assert_eq!(highlighted_edits.highlights.len(), 3);
24226            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
24227            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
24228            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
24229            for highlight in &highlighted_edits.highlights {
24230                assert_eq!(
24231                    highlight.1.background_color,
24232                    Some(cx.theme().status().created_background)
24233                );
24234            }
24235        },
24236    )
24237    .await;
24238}
24239
24240#[gpui::test]
24241async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
24242    init_test(cx, |_| {});
24243
24244    // Deletion
24245    assert_highlighted_edits(
24246        "Hello, world!",
24247        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
24248        true,
24249        cx,
24250        |highlighted_edits, cx| {
24251            assert_eq!(highlighted_edits.text, "Hello, world!");
24252            assert_eq!(highlighted_edits.highlights.len(), 1);
24253            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
24254            assert_eq!(
24255                highlighted_edits.highlights[0].1.background_color,
24256                Some(cx.theme().status().deleted_background)
24257            );
24258        },
24259    )
24260    .await;
24261
24262    // Insertion
24263    assert_highlighted_edits(
24264        "Hello, world!",
24265        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
24266        true,
24267        cx,
24268        |highlighted_edits, cx| {
24269            assert_eq!(highlighted_edits.highlights.len(), 1);
24270            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
24271            assert_eq!(
24272                highlighted_edits.highlights[0].1.background_color,
24273                Some(cx.theme().status().created_background)
24274            );
24275        },
24276    )
24277    .await;
24278}
24279
24280async fn assert_highlighted_edits(
24281    text: &str,
24282    edits: Vec<(Range<Point>, String)>,
24283    include_deletions: bool,
24284    cx: &mut TestAppContext,
24285    assertion_fn: impl Fn(HighlightedText, &App),
24286) {
24287    let window = cx.add_window(|window, cx| {
24288        let buffer = MultiBuffer::build_simple(text, cx);
24289        Editor::new(EditorMode::full(), buffer, None, window, cx)
24290    });
24291    let cx = &mut VisualTestContext::from_window(*window, cx);
24292
24293    let (buffer, snapshot) = window
24294        .update(cx, |editor, _window, cx| {
24295            (
24296                editor.buffer().clone(),
24297                editor.buffer().read(cx).snapshot(cx),
24298            )
24299        })
24300        .unwrap();
24301
24302    let edits = edits
24303        .into_iter()
24304        .map(|(range, edit)| {
24305            (
24306                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24307                edit,
24308            )
24309        })
24310        .collect::<Vec<_>>();
24311
24312    let text_anchor_edits = edits
24313        .clone()
24314        .into_iter()
24315        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24316        .collect::<Vec<_>>();
24317
24318    let edit_preview = window
24319        .update(cx, |_, _window, cx| {
24320            buffer
24321                .read(cx)
24322                .as_singleton()
24323                .unwrap()
24324                .read(cx)
24325                .preview_edits(text_anchor_edits.into(), cx)
24326        })
24327        .unwrap()
24328        .await;
24329
24330    cx.update(|_window, cx| {
24331        let highlighted_edits = edit_prediction_edit_text(
24332            snapshot.as_singleton().unwrap().2,
24333            &edits,
24334            &edit_preview,
24335            include_deletions,
24336            cx,
24337        );
24338        assertion_fn(highlighted_edits, cx)
24339    });
24340}
24341
24342#[track_caller]
24343fn assert_breakpoint(
24344    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24345    path: &Arc<Path>,
24346    expected: Vec<(u32, Breakpoint)>,
24347) {
24348    if expected.is_empty() {
24349        assert!(!breakpoints.contains_key(path), "{}", path.display());
24350    } else {
24351        let mut breakpoint = breakpoints
24352            .get(path)
24353            .unwrap()
24354            .iter()
24355            .map(|breakpoint| {
24356                (
24357                    breakpoint.row,
24358                    Breakpoint {
24359                        message: breakpoint.message.clone(),
24360                        state: breakpoint.state,
24361                        condition: breakpoint.condition.clone(),
24362                        hit_condition: breakpoint.hit_condition.clone(),
24363                    },
24364                )
24365            })
24366            .collect::<Vec<_>>();
24367
24368        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24369
24370        assert_eq!(expected, breakpoint);
24371    }
24372}
24373
24374fn add_log_breakpoint_at_cursor(
24375    editor: &mut Editor,
24376    log_message: &str,
24377    window: &mut Window,
24378    cx: &mut Context<Editor>,
24379) {
24380    let (anchor, bp) = editor
24381        .breakpoints_at_cursors(window, cx)
24382        .first()
24383        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24384        .unwrap_or_else(|| {
24385            let snapshot = editor.snapshot(window, cx);
24386            let cursor_position: Point =
24387                editor.selections.newest(&snapshot.display_snapshot).head();
24388
24389            let breakpoint_position = snapshot
24390                .buffer_snapshot()
24391                .anchor_before(Point::new(cursor_position.row, 0));
24392
24393            (breakpoint_position, Breakpoint::new_log(log_message))
24394        });
24395
24396    editor.edit_breakpoint_at_anchor(
24397        anchor,
24398        bp,
24399        BreakpointEditAction::EditLogMessage(log_message.into()),
24400        cx,
24401    );
24402}
24403
24404#[gpui::test]
24405async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24406    init_test(cx, |_| {});
24407
24408    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24409    let fs = FakeFs::new(cx.executor());
24410    fs.insert_tree(
24411        path!("/a"),
24412        json!({
24413            "main.rs": sample_text,
24414        }),
24415    )
24416    .await;
24417    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24418    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24419    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24420
24421    let fs = FakeFs::new(cx.executor());
24422    fs.insert_tree(
24423        path!("/a"),
24424        json!({
24425            "main.rs": sample_text,
24426        }),
24427    )
24428    .await;
24429    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24430    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24431    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24432    let worktree_id = workspace
24433        .update(cx, |workspace, _window, cx| {
24434            workspace.project().update(cx, |project, cx| {
24435                project.worktrees(cx).next().unwrap().read(cx).id()
24436            })
24437        })
24438        .unwrap();
24439
24440    let buffer = project
24441        .update(cx, |project, cx| {
24442            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24443        })
24444        .await
24445        .unwrap();
24446
24447    let (editor, cx) = cx.add_window_view(|window, cx| {
24448        Editor::new(
24449            EditorMode::full(),
24450            MultiBuffer::build_from_buffer(buffer, cx),
24451            Some(project.clone()),
24452            window,
24453            cx,
24454        )
24455    });
24456
24457    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24458    let abs_path = project.read_with(cx, |project, cx| {
24459        project
24460            .absolute_path(&project_path, cx)
24461            .map(Arc::from)
24462            .unwrap()
24463    });
24464
24465    // assert we can add breakpoint on the first line
24466    editor.update_in(cx, |editor, window, cx| {
24467        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24468        editor.move_to_end(&MoveToEnd, window, cx);
24469        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24470    });
24471
24472    let breakpoints = editor.update(cx, |editor, cx| {
24473        editor
24474            .breakpoint_store()
24475            .as_ref()
24476            .unwrap()
24477            .read(cx)
24478            .all_source_breakpoints(cx)
24479    });
24480
24481    assert_eq!(1, breakpoints.len());
24482    assert_breakpoint(
24483        &breakpoints,
24484        &abs_path,
24485        vec![
24486            (0, Breakpoint::new_standard()),
24487            (3, Breakpoint::new_standard()),
24488        ],
24489    );
24490
24491    editor.update_in(cx, |editor, window, cx| {
24492        editor.move_to_beginning(&MoveToBeginning, window, cx);
24493        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24494    });
24495
24496    let breakpoints = editor.update(cx, |editor, cx| {
24497        editor
24498            .breakpoint_store()
24499            .as_ref()
24500            .unwrap()
24501            .read(cx)
24502            .all_source_breakpoints(cx)
24503    });
24504
24505    assert_eq!(1, breakpoints.len());
24506    assert_breakpoint(
24507        &breakpoints,
24508        &abs_path,
24509        vec![(3, Breakpoint::new_standard())],
24510    );
24511
24512    editor.update_in(cx, |editor, window, cx| {
24513        editor.move_to_end(&MoveToEnd, window, cx);
24514        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24515    });
24516
24517    let breakpoints = editor.update(cx, |editor, cx| {
24518        editor
24519            .breakpoint_store()
24520            .as_ref()
24521            .unwrap()
24522            .read(cx)
24523            .all_source_breakpoints(cx)
24524    });
24525
24526    assert_eq!(0, breakpoints.len());
24527    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24528}
24529
24530#[gpui::test]
24531async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24532    init_test(cx, |_| {});
24533
24534    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24535
24536    let fs = FakeFs::new(cx.executor());
24537    fs.insert_tree(
24538        path!("/a"),
24539        json!({
24540            "main.rs": sample_text,
24541        }),
24542    )
24543    .await;
24544    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24545    let (workspace, cx) =
24546        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24547
24548    let worktree_id = workspace.update(cx, |workspace, cx| {
24549        workspace.project().update(cx, |project, cx| {
24550            project.worktrees(cx).next().unwrap().read(cx).id()
24551        })
24552    });
24553
24554    let buffer = project
24555        .update(cx, |project, cx| {
24556            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24557        })
24558        .await
24559        .unwrap();
24560
24561    let (editor, cx) = cx.add_window_view(|window, cx| {
24562        Editor::new(
24563            EditorMode::full(),
24564            MultiBuffer::build_from_buffer(buffer, cx),
24565            Some(project.clone()),
24566            window,
24567            cx,
24568        )
24569    });
24570
24571    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24572    let abs_path = project.read_with(cx, |project, cx| {
24573        project
24574            .absolute_path(&project_path, cx)
24575            .map(Arc::from)
24576            .unwrap()
24577    });
24578
24579    editor.update_in(cx, |editor, window, cx| {
24580        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24581    });
24582
24583    let breakpoints = editor.update(cx, |editor, cx| {
24584        editor
24585            .breakpoint_store()
24586            .as_ref()
24587            .unwrap()
24588            .read(cx)
24589            .all_source_breakpoints(cx)
24590    });
24591
24592    assert_breakpoint(
24593        &breakpoints,
24594        &abs_path,
24595        vec![(0, Breakpoint::new_log("hello world"))],
24596    );
24597
24598    // Removing a log message from a log breakpoint should remove it
24599    editor.update_in(cx, |editor, window, cx| {
24600        add_log_breakpoint_at_cursor(editor, "", window, cx);
24601    });
24602
24603    let breakpoints = editor.update(cx, |editor, cx| {
24604        editor
24605            .breakpoint_store()
24606            .as_ref()
24607            .unwrap()
24608            .read(cx)
24609            .all_source_breakpoints(cx)
24610    });
24611
24612    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24613
24614    editor.update_in(cx, |editor, window, cx| {
24615        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24616        editor.move_to_end(&MoveToEnd, window, cx);
24617        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24618        // Not adding a log message to a standard breakpoint shouldn't remove it
24619        add_log_breakpoint_at_cursor(editor, "", window, cx);
24620    });
24621
24622    let breakpoints = editor.update(cx, |editor, cx| {
24623        editor
24624            .breakpoint_store()
24625            .as_ref()
24626            .unwrap()
24627            .read(cx)
24628            .all_source_breakpoints(cx)
24629    });
24630
24631    assert_breakpoint(
24632        &breakpoints,
24633        &abs_path,
24634        vec![
24635            (0, Breakpoint::new_standard()),
24636            (3, Breakpoint::new_standard()),
24637        ],
24638    );
24639
24640    editor.update_in(cx, |editor, window, cx| {
24641        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24642    });
24643
24644    let breakpoints = editor.update(cx, |editor, cx| {
24645        editor
24646            .breakpoint_store()
24647            .as_ref()
24648            .unwrap()
24649            .read(cx)
24650            .all_source_breakpoints(cx)
24651    });
24652
24653    assert_breakpoint(
24654        &breakpoints,
24655        &abs_path,
24656        vec![
24657            (0, Breakpoint::new_standard()),
24658            (3, Breakpoint::new_log("hello world")),
24659        ],
24660    );
24661
24662    editor.update_in(cx, |editor, window, cx| {
24663        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24664    });
24665
24666    let breakpoints = editor.update(cx, |editor, cx| {
24667        editor
24668            .breakpoint_store()
24669            .as_ref()
24670            .unwrap()
24671            .read(cx)
24672            .all_source_breakpoints(cx)
24673    });
24674
24675    assert_breakpoint(
24676        &breakpoints,
24677        &abs_path,
24678        vec![
24679            (0, Breakpoint::new_standard()),
24680            (3, Breakpoint::new_log("hello Earth!!")),
24681        ],
24682    );
24683}
24684
24685/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24686/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24687/// or when breakpoints were placed out of order. This tests for a regression too
24688#[gpui::test]
24689async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24690    init_test(cx, |_| {});
24691
24692    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24693    let fs = FakeFs::new(cx.executor());
24694    fs.insert_tree(
24695        path!("/a"),
24696        json!({
24697            "main.rs": sample_text,
24698        }),
24699    )
24700    .await;
24701    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24702    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24703    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24704
24705    let fs = FakeFs::new(cx.executor());
24706    fs.insert_tree(
24707        path!("/a"),
24708        json!({
24709            "main.rs": sample_text,
24710        }),
24711    )
24712    .await;
24713    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24714    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24715    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24716    let worktree_id = workspace
24717        .update(cx, |workspace, _window, cx| {
24718            workspace.project().update(cx, |project, cx| {
24719                project.worktrees(cx).next().unwrap().read(cx).id()
24720            })
24721        })
24722        .unwrap();
24723
24724    let buffer = project
24725        .update(cx, |project, cx| {
24726            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24727        })
24728        .await
24729        .unwrap();
24730
24731    let (editor, cx) = cx.add_window_view(|window, cx| {
24732        Editor::new(
24733            EditorMode::full(),
24734            MultiBuffer::build_from_buffer(buffer, cx),
24735            Some(project.clone()),
24736            window,
24737            cx,
24738        )
24739    });
24740
24741    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24742    let abs_path = project.read_with(cx, |project, cx| {
24743        project
24744            .absolute_path(&project_path, cx)
24745            .map(Arc::from)
24746            .unwrap()
24747    });
24748
24749    // assert we can add breakpoint on the first line
24750    editor.update_in(cx, |editor, window, cx| {
24751        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24752        editor.move_to_end(&MoveToEnd, window, cx);
24753        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24754        editor.move_up(&MoveUp, window, cx);
24755        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24756    });
24757
24758    let breakpoints = editor.update(cx, |editor, cx| {
24759        editor
24760            .breakpoint_store()
24761            .as_ref()
24762            .unwrap()
24763            .read(cx)
24764            .all_source_breakpoints(cx)
24765    });
24766
24767    assert_eq!(1, breakpoints.len());
24768    assert_breakpoint(
24769        &breakpoints,
24770        &abs_path,
24771        vec![
24772            (0, Breakpoint::new_standard()),
24773            (2, Breakpoint::new_standard()),
24774            (3, Breakpoint::new_standard()),
24775        ],
24776    );
24777
24778    editor.update_in(cx, |editor, window, cx| {
24779        editor.move_to_beginning(&MoveToBeginning, window, cx);
24780        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24781        editor.move_to_end(&MoveToEnd, window, cx);
24782        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24783        // Disabling a breakpoint that doesn't exist should do nothing
24784        editor.move_up(&MoveUp, window, cx);
24785        editor.move_up(&MoveUp, window, cx);
24786        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24787    });
24788
24789    let breakpoints = editor.update(cx, |editor, cx| {
24790        editor
24791            .breakpoint_store()
24792            .as_ref()
24793            .unwrap()
24794            .read(cx)
24795            .all_source_breakpoints(cx)
24796    });
24797
24798    let disable_breakpoint = {
24799        let mut bp = Breakpoint::new_standard();
24800        bp.state = BreakpointState::Disabled;
24801        bp
24802    };
24803
24804    assert_eq!(1, breakpoints.len());
24805    assert_breakpoint(
24806        &breakpoints,
24807        &abs_path,
24808        vec![
24809            (0, disable_breakpoint.clone()),
24810            (2, Breakpoint::new_standard()),
24811            (3, disable_breakpoint.clone()),
24812        ],
24813    );
24814
24815    editor.update_in(cx, |editor, window, cx| {
24816        editor.move_to_beginning(&MoveToBeginning, window, cx);
24817        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24818        editor.move_to_end(&MoveToEnd, window, cx);
24819        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24820        editor.move_up(&MoveUp, window, cx);
24821        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24822    });
24823
24824    let breakpoints = editor.update(cx, |editor, cx| {
24825        editor
24826            .breakpoint_store()
24827            .as_ref()
24828            .unwrap()
24829            .read(cx)
24830            .all_source_breakpoints(cx)
24831    });
24832
24833    assert_eq!(1, breakpoints.len());
24834    assert_breakpoint(
24835        &breakpoints,
24836        &abs_path,
24837        vec![
24838            (0, Breakpoint::new_standard()),
24839            (2, disable_breakpoint),
24840            (3, Breakpoint::new_standard()),
24841        ],
24842    );
24843}
24844
24845#[gpui::test]
24846async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
24847    init_test(cx, |_| {});
24848
24849    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24850    let fs = FakeFs::new(cx.executor());
24851    fs.insert_tree(
24852        path!("/a"),
24853        json!({
24854            "main.rs": sample_text,
24855        }),
24856    )
24857    .await;
24858    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24859    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24860    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24861    let worktree_id = workspace
24862        .update(cx, |workspace, _window, cx| {
24863            workspace.project().update(cx, |project, cx| {
24864                project.worktrees(cx).next().unwrap().read(cx).id()
24865            })
24866        })
24867        .unwrap();
24868
24869    let buffer = project
24870        .update(cx, |project, cx| {
24871            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24872        })
24873        .await
24874        .unwrap();
24875
24876    let (editor, cx) = cx.add_window_view(|window, cx| {
24877        Editor::new(
24878            EditorMode::full(),
24879            MultiBuffer::build_from_buffer(buffer, cx),
24880            Some(project.clone()),
24881            window,
24882            cx,
24883        )
24884    });
24885
24886    // Simulate hovering over row 0 with no existing breakpoint.
24887    editor.update(cx, |editor, _cx| {
24888        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
24889            display_row: DisplayRow(0),
24890            is_active: true,
24891            collides_with_existing_breakpoint: false,
24892        });
24893    });
24894
24895    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
24896    editor.update_in(cx, |editor, window, cx| {
24897        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24898    });
24899    editor.update(cx, |editor, _cx| {
24900        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
24901        assert!(
24902            indicator.collides_with_existing_breakpoint,
24903            "Adding a breakpoint on the hovered row should set collision to true"
24904        );
24905    });
24906
24907    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
24908    editor.update_in(cx, |editor, window, cx| {
24909        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24910    });
24911    editor.update(cx, |editor, _cx| {
24912        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
24913        assert!(
24914            !indicator.collides_with_existing_breakpoint,
24915            "Removing a breakpoint on the hovered row should set collision to false"
24916        );
24917    });
24918
24919    // Now move cursor to row 2 while phantom indicator stays on row 0.
24920    editor.update_in(cx, |editor, window, cx| {
24921        editor.move_down(&MoveDown, window, cx);
24922        editor.move_down(&MoveDown, window, cx);
24923    });
24924
24925    // Ensure phantom indicator is still on row 0, not colliding.
24926    editor.update(cx, |editor, _cx| {
24927        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
24928            display_row: DisplayRow(0),
24929            is_active: true,
24930            collides_with_existing_breakpoint: false,
24931        });
24932    });
24933
24934    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
24935    editor.update_in(cx, |editor, window, cx| {
24936        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24937    });
24938    editor.update(cx, |editor, _cx| {
24939        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
24940        assert!(
24941            !indicator.collides_with_existing_breakpoint,
24942            "Toggling a breakpoint on a different row should not affect the phantom indicator"
24943        );
24944    });
24945}
24946
24947#[gpui::test]
24948async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24949    init_test(cx, |_| {});
24950    let capabilities = lsp::ServerCapabilities {
24951        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24952            prepare_provider: Some(true),
24953            work_done_progress_options: Default::default(),
24954        })),
24955        ..Default::default()
24956    };
24957    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24958
24959    cx.set_state(indoc! {"
24960        struct Fˇoo {}
24961    "});
24962
24963    cx.update_editor(|editor, _, cx| {
24964        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24965        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24966        editor.highlight_background(
24967            HighlightKey::DocumentHighlightRead,
24968            &[highlight_range],
24969            |_, theme| theme.colors().editor_document_highlight_read_background,
24970            cx,
24971        );
24972    });
24973
24974    let mut prepare_rename_handler = cx
24975        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24976            move |_, _, _| async move {
24977                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24978                    start: lsp::Position {
24979                        line: 0,
24980                        character: 7,
24981                    },
24982                    end: lsp::Position {
24983                        line: 0,
24984                        character: 10,
24985                    },
24986                })))
24987            },
24988        );
24989    let prepare_rename_task = cx
24990        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24991        .expect("Prepare rename was not started");
24992    prepare_rename_handler.next().await.unwrap();
24993    prepare_rename_task.await.expect("Prepare rename failed");
24994
24995    let mut rename_handler =
24996        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24997            let edit = lsp::TextEdit {
24998                range: lsp::Range {
24999                    start: lsp::Position {
25000                        line: 0,
25001                        character: 7,
25002                    },
25003                    end: lsp::Position {
25004                        line: 0,
25005                        character: 10,
25006                    },
25007                },
25008                new_text: "FooRenamed".to_string(),
25009            };
25010            Ok(Some(lsp::WorkspaceEdit::new(
25011                // Specify the same edit twice
25012                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
25013            )))
25014        });
25015    let rename_task = cx
25016        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
25017        .expect("Confirm rename was not started");
25018    rename_handler.next().await.unwrap();
25019    rename_task.await.expect("Confirm rename failed");
25020    cx.run_until_parked();
25021
25022    // Despite two edits, only one is actually applied as those are identical
25023    cx.assert_editor_state(indoc! {"
25024        struct FooRenamedˇ {}
25025    "});
25026}
25027
25028#[gpui::test]
25029async fn test_rename_without_prepare(cx: &mut TestAppContext) {
25030    init_test(cx, |_| {});
25031    // These capabilities indicate that the server does not support prepare rename.
25032    let capabilities = lsp::ServerCapabilities {
25033        rename_provider: Some(lsp::OneOf::Left(true)),
25034        ..Default::default()
25035    };
25036    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
25037
25038    cx.set_state(indoc! {"
25039        struct Fˇoo {}
25040    "});
25041
25042    cx.update_editor(|editor, _window, cx| {
25043        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
25044        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
25045        editor.highlight_background(
25046            HighlightKey::DocumentHighlightRead,
25047            &[highlight_range],
25048            |_, theme| theme.colors().editor_document_highlight_read_background,
25049            cx,
25050        );
25051    });
25052
25053    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
25054        .expect("Prepare rename was not started")
25055        .await
25056        .expect("Prepare rename failed");
25057
25058    let mut rename_handler =
25059        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
25060            let edit = lsp::TextEdit {
25061                range: lsp::Range {
25062                    start: lsp::Position {
25063                        line: 0,
25064                        character: 7,
25065                    },
25066                    end: lsp::Position {
25067                        line: 0,
25068                        character: 10,
25069                    },
25070                },
25071                new_text: "FooRenamed".to_string(),
25072            };
25073            Ok(Some(lsp::WorkspaceEdit::new(
25074                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
25075            )))
25076        });
25077    let rename_task = cx
25078        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
25079        .expect("Confirm rename was not started");
25080    rename_handler.next().await.unwrap();
25081    rename_task.await.expect("Confirm rename failed");
25082    cx.run_until_parked();
25083
25084    // Correct range is renamed, as `surrounding_word` is used to find it.
25085    cx.assert_editor_state(indoc! {"
25086        struct FooRenamedˇ {}
25087    "});
25088}
25089
25090#[gpui::test]
25091async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
25092    init_test(cx, |_| {});
25093    let mut cx = EditorTestContext::new(cx).await;
25094
25095    let language = Arc::new(
25096        Language::new(
25097            LanguageConfig::default(),
25098            Some(tree_sitter_html::LANGUAGE.into()),
25099        )
25100        .with_brackets_query(
25101            r#"
25102            ("<" @open "/>" @close)
25103            ("</" @open ">" @close)
25104            ("<" @open ">" @close)
25105            ("\"" @open "\"" @close)
25106            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
25107        "#,
25108        )
25109        .unwrap(),
25110    );
25111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25112
25113    cx.set_state(indoc! {"
25114        <span>ˇ</span>
25115    "});
25116    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25117    cx.assert_editor_state(indoc! {"
25118        <span>
25119        ˇ
25120        </span>
25121    "});
25122
25123    cx.set_state(indoc! {"
25124        <span><span></span>ˇ</span>
25125    "});
25126    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25127    cx.assert_editor_state(indoc! {"
25128        <span><span></span>
25129        ˇ</span>
25130    "});
25131
25132    cx.set_state(indoc! {"
25133        <span>ˇ
25134        </span>
25135    "});
25136    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25137    cx.assert_editor_state(indoc! {"
25138        <span>
25139        ˇ
25140        </span>
25141    "});
25142}
25143
25144#[gpui::test(iterations = 10)]
25145async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
25146    init_test(cx, |_| {});
25147
25148    let fs = FakeFs::new(cx.executor());
25149    fs.insert_tree(
25150        path!("/dir"),
25151        json!({
25152            "a.ts": "a",
25153        }),
25154    )
25155    .await;
25156
25157    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
25158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25159    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25160
25161    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25162    language_registry.add(Arc::new(Language::new(
25163        LanguageConfig {
25164            name: "TypeScript".into(),
25165            matcher: LanguageMatcher {
25166                path_suffixes: vec!["ts".to_string()],
25167                ..Default::default()
25168            },
25169            ..Default::default()
25170        },
25171        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
25172    )));
25173    let mut fake_language_servers = language_registry.register_fake_lsp(
25174        "TypeScript",
25175        FakeLspAdapter {
25176            capabilities: lsp::ServerCapabilities {
25177                code_lens_provider: Some(lsp::CodeLensOptions {
25178                    resolve_provider: Some(true),
25179                }),
25180                execute_command_provider: Some(lsp::ExecuteCommandOptions {
25181                    commands: vec!["_the/command".to_string()],
25182                    ..lsp::ExecuteCommandOptions::default()
25183                }),
25184                ..lsp::ServerCapabilities::default()
25185            },
25186            ..FakeLspAdapter::default()
25187        },
25188    );
25189
25190    let editor = workspace
25191        .update(cx, |workspace, window, cx| {
25192            workspace.open_abs_path(
25193                PathBuf::from(path!("/dir/a.ts")),
25194                OpenOptions::default(),
25195                window,
25196                cx,
25197            )
25198        })
25199        .unwrap()
25200        .await
25201        .unwrap()
25202        .downcast::<Editor>()
25203        .unwrap();
25204    cx.executor().run_until_parked();
25205
25206    let fake_server = fake_language_servers.next().await.unwrap();
25207
25208    let buffer = editor.update(cx, |editor, cx| {
25209        editor
25210            .buffer()
25211            .read(cx)
25212            .as_singleton()
25213            .expect("have opened a single file by path")
25214    });
25215
25216    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
25217    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
25218    drop(buffer_snapshot);
25219    let actions = cx
25220        .update_window(*workspace, |_, window, cx| {
25221            project.code_actions(&buffer, anchor..anchor, window, cx)
25222        })
25223        .unwrap();
25224
25225    fake_server
25226        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25227            Ok(Some(vec![
25228                lsp::CodeLens {
25229                    range: lsp::Range::default(),
25230                    command: Some(lsp::Command {
25231                        title: "Code lens command".to_owned(),
25232                        command: "_the/command".to_owned(),
25233                        arguments: None,
25234                    }),
25235                    data: None,
25236                },
25237                lsp::CodeLens {
25238                    range: lsp::Range::default(),
25239                    command: Some(lsp::Command {
25240                        title: "Command not in capabilities".to_owned(),
25241                        command: "not in capabilities".to_owned(),
25242                        arguments: None,
25243                    }),
25244                    data: None,
25245                },
25246                lsp::CodeLens {
25247                    range: lsp::Range {
25248                        start: lsp::Position {
25249                            line: 1,
25250                            character: 1,
25251                        },
25252                        end: lsp::Position {
25253                            line: 1,
25254                            character: 1,
25255                        },
25256                    },
25257                    command: Some(lsp::Command {
25258                        title: "Command not in range".to_owned(),
25259                        command: "_the/command".to_owned(),
25260                        arguments: None,
25261                    }),
25262                    data: None,
25263                },
25264            ]))
25265        })
25266        .next()
25267        .await;
25268
25269    let actions = actions.await.unwrap();
25270    assert_eq!(
25271        actions.len(),
25272        1,
25273        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
25274    );
25275    let action = actions[0].clone();
25276    let apply = project.update(cx, |project, cx| {
25277        project.apply_code_action(buffer.clone(), action, true, cx)
25278    });
25279
25280    // Resolving the code action does not populate its edits. In absence of
25281    // edits, we must execute the given command.
25282    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
25283        |mut lens, _| async move {
25284            let lens_command = lens.command.as_mut().expect("should have a command");
25285            assert_eq!(lens_command.title, "Code lens command");
25286            lens_command.arguments = Some(vec![json!("the-argument")]);
25287            Ok(lens)
25288        },
25289    );
25290
25291    // While executing the command, the language server sends the editor
25292    // a `workspaceEdit` request.
25293    fake_server
25294        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
25295            let fake = fake_server.clone();
25296            move |params, _| {
25297                assert_eq!(params.command, "_the/command");
25298                let fake = fake.clone();
25299                async move {
25300                    fake.server
25301                        .request::<lsp::request::ApplyWorkspaceEdit>(
25302                            lsp::ApplyWorkspaceEditParams {
25303                                label: None,
25304                                edit: lsp::WorkspaceEdit {
25305                                    changes: Some(
25306                                        [(
25307                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
25308                                            vec![lsp::TextEdit {
25309                                                range: lsp::Range::new(
25310                                                    lsp::Position::new(0, 0),
25311                                                    lsp::Position::new(0, 0),
25312                                                ),
25313                                                new_text: "X".into(),
25314                                            }],
25315                                        )]
25316                                        .into_iter()
25317                                        .collect(),
25318                                    ),
25319                                    ..lsp::WorkspaceEdit::default()
25320                                },
25321                            },
25322                            DEFAULT_LSP_REQUEST_TIMEOUT,
25323                        )
25324                        .await
25325                        .into_response()
25326                        .unwrap();
25327                    Ok(Some(json!(null)))
25328                }
25329            }
25330        })
25331        .next()
25332        .await;
25333
25334    // Applying the code lens command returns a project transaction containing the edits
25335    // sent by the language server in its `workspaceEdit` request.
25336    let transaction = apply.await.unwrap();
25337    assert!(transaction.0.contains_key(&buffer));
25338    buffer.update(cx, |buffer, cx| {
25339        assert_eq!(buffer.text(), "Xa");
25340        buffer.undo(cx);
25341        assert_eq!(buffer.text(), "a");
25342    });
25343
25344    let actions_after_edits = cx
25345        .update_window(*workspace, |_, window, cx| {
25346            project.code_actions(&buffer, anchor..anchor, window, cx)
25347        })
25348        .unwrap()
25349        .await
25350        .unwrap();
25351    assert_eq!(
25352        actions, actions_after_edits,
25353        "For the same selection, same code lens actions should be returned"
25354    );
25355
25356    let _responses =
25357        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25358            panic!("No more code lens requests are expected");
25359        });
25360    editor.update_in(cx, |editor, window, cx| {
25361        editor.select_all(&SelectAll, window, cx);
25362    });
25363    cx.executor().run_until_parked();
25364    let new_actions = cx
25365        .update_window(*workspace, |_, window, cx| {
25366            project.code_actions(&buffer, anchor..anchor, window, cx)
25367        })
25368        .unwrap()
25369        .await
25370        .unwrap();
25371    assert_eq!(
25372        actions, new_actions,
25373        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
25374    );
25375}
25376
25377#[gpui::test]
25378async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
25379    init_test(cx, |_| {});
25380
25381    let fs = FakeFs::new(cx.executor());
25382    let main_text = r#"fn main() {
25383println!("1");
25384println!("2");
25385println!("3");
25386println!("4");
25387println!("5");
25388}"#;
25389    let lib_text = "mod foo {}";
25390    fs.insert_tree(
25391        path!("/a"),
25392        json!({
25393            "lib.rs": lib_text,
25394            "main.rs": main_text,
25395        }),
25396    )
25397    .await;
25398
25399    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25400    let (workspace, cx) =
25401        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25402    let worktree_id = workspace.update(cx, |workspace, cx| {
25403        workspace.project().update(cx, |project, cx| {
25404            project.worktrees(cx).next().unwrap().read(cx).id()
25405        })
25406    });
25407
25408    let expected_ranges = vec![
25409        Point::new(0, 0)..Point::new(0, 0),
25410        Point::new(1, 0)..Point::new(1, 1),
25411        Point::new(2, 0)..Point::new(2, 2),
25412        Point::new(3, 0)..Point::new(3, 3),
25413    ];
25414
25415    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25416    let editor_1 = workspace
25417        .update_in(cx, |workspace, window, cx| {
25418            workspace.open_path(
25419                (worktree_id, rel_path("main.rs")),
25420                Some(pane_1.downgrade()),
25421                true,
25422                window,
25423                cx,
25424            )
25425        })
25426        .unwrap()
25427        .await
25428        .downcast::<Editor>()
25429        .unwrap();
25430    pane_1.update(cx, |pane, cx| {
25431        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25432        open_editor.update(cx, |editor, cx| {
25433            assert_eq!(
25434                editor.display_text(cx),
25435                main_text,
25436                "Original main.rs text on initial open",
25437            );
25438            assert_eq!(
25439                editor
25440                    .selections
25441                    .all::<Point>(&editor.display_snapshot(cx))
25442                    .into_iter()
25443                    .map(|s| s.range())
25444                    .collect::<Vec<_>>(),
25445                vec![Point::zero()..Point::zero()],
25446                "Default selections on initial open",
25447            );
25448        })
25449    });
25450    editor_1.update_in(cx, |editor, window, cx| {
25451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25452            s.select_ranges(expected_ranges.clone());
25453        });
25454    });
25455
25456    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25457        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25458    });
25459    let editor_2 = workspace
25460        .update_in(cx, |workspace, window, cx| {
25461            workspace.open_path(
25462                (worktree_id, rel_path("main.rs")),
25463                Some(pane_2.downgrade()),
25464                true,
25465                window,
25466                cx,
25467            )
25468        })
25469        .unwrap()
25470        .await
25471        .downcast::<Editor>()
25472        .unwrap();
25473    pane_2.update(cx, |pane, cx| {
25474        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25475        open_editor.update(cx, |editor, cx| {
25476            assert_eq!(
25477                editor.display_text(cx),
25478                main_text,
25479                "Original main.rs text on initial open in another panel",
25480            );
25481            assert_eq!(
25482                editor
25483                    .selections
25484                    .all::<Point>(&editor.display_snapshot(cx))
25485                    .into_iter()
25486                    .map(|s| s.range())
25487                    .collect::<Vec<_>>(),
25488                vec![Point::zero()..Point::zero()],
25489                "Default selections on initial open in another panel",
25490            );
25491        })
25492    });
25493
25494    editor_2.update_in(cx, |editor, window, cx| {
25495        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25496    });
25497
25498    let _other_editor_1 = workspace
25499        .update_in(cx, |workspace, window, cx| {
25500            workspace.open_path(
25501                (worktree_id, rel_path("lib.rs")),
25502                Some(pane_1.downgrade()),
25503                true,
25504                window,
25505                cx,
25506            )
25507        })
25508        .unwrap()
25509        .await
25510        .downcast::<Editor>()
25511        .unwrap();
25512    pane_1
25513        .update_in(cx, |pane, window, cx| {
25514            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25515        })
25516        .await
25517        .unwrap();
25518    drop(editor_1);
25519    pane_1.update(cx, |pane, cx| {
25520        pane.active_item()
25521            .unwrap()
25522            .downcast::<Editor>()
25523            .unwrap()
25524            .update(cx, |editor, cx| {
25525                assert_eq!(
25526                    editor.display_text(cx),
25527                    lib_text,
25528                    "Other file should be open and active",
25529                );
25530            });
25531        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25532    });
25533
25534    let _other_editor_2 = workspace
25535        .update_in(cx, |workspace, window, cx| {
25536            workspace.open_path(
25537                (worktree_id, rel_path("lib.rs")),
25538                Some(pane_2.downgrade()),
25539                true,
25540                window,
25541                cx,
25542            )
25543        })
25544        .unwrap()
25545        .await
25546        .downcast::<Editor>()
25547        .unwrap();
25548    pane_2
25549        .update_in(cx, |pane, window, cx| {
25550            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25551        })
25552        .await
25553        .unwrap();
25554    drop(editor_2);
25555    pane_2.update(cx, |pane, cx| {
25556        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25557        open_editor.update(cx, |editor, cx| {
25558            assert_eq!(
25559                editor.display_text(cx),
25560                lib_text,
25561                "Other file should be open and active in another panel too",
25562            );
25563        });
25564        assert_eq!(
25565            pane.items().count(),
25566            1,
25567            "No other editors should be open in another pane",
25568        );
25569    });
25570
25571    let _editor_1_reopened = workspace
25572        .update_in(cx, |workspace, window, cx| {
25573            workspace.open_path(
25574                (worktree_id, rel_path("main.rs")),
25575                Some(pane_1.downgrade()),
25576                true,
25577                window,
25578                cx,
25579            )
25580        })
25581        .unwrap()
25582        .await
25583        .downcast::<Editor>()
25584        .unwrap();
25585    let _editor_2_reopened = workspace
25586        .update_in(cx, |workspace, window, cx| {
25587            workspace.open_path(
25588                (worktree_id, rel_path("main.rs")),
25589                Some(pane_2.downgrade()),
25590                true,
25591                window,
25592                cx,
25593            )
25594        })
25595        .unwrap()
25596        .await
25597        .downcast::<Editor>()
25598        .unwrap();
25599    pane_1.update(cx, |pane, cx| {
25600        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25601        open_editor.update(cx, |editor, cx| {
25602            assert_eq!(
25603                editor.display_text(cx),
25604                main_text,
25605                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25606            );
25607            assert_eq!(
25608                editor
25609                    .selections
25610                    .all::<Point>(&editor.display_snapshot(cx))
25611                    .into_iter()
25612                    .map(|s| s.range())
25613                    .collect::<Vec<_>>(),
25614                expected_ranges,
25615                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25616            );
25617        })
25618    });
25619    pane_2.update(cx, |pane, cx| {
25620        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25621        open_editor.update(cx, |editor, cx| {
25622            assert_eq!(
25623                editor.display_text(cx),
25624                r#"fn main() {
25625⋯rintln!("1");
25626⋯intln!("2");
25627⋯ntln!("3");
25628println!("4");
25629println!("5");
25630}"#,
25631                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25632            );
25633            assert_eq!(
25634                editor
25635                    .selections
25636                    .all::<Point>(&editor.display_snapshot(cx))
25637                    .into_iter()
25638                    .map(|s| s.range())
25639                    .collect::<Vec<_>>(),
25640                vec![Point::zero()..Point::zero()],
25641                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25642            );
25643        })
25644    });
25645}
25646
25647#[gpui::test]
25648async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25649    init_test(cx, |_| {});
25650
25651    let fs = FakeFs::new(cx.executor());
25652    let main_text = r#"fn main() {
25653println!("1");
25654println!("2");
25655println!("3");
25656println!("4");
25657println!("5");
25658}"#;
25659    let lib_text = "mod foo {}";
25660    fs.insert_tree(
25661        path!("/a"),
25662        json!({
25663            "lib.rs": lib_text,
25664            "main.rs": main_text,
25665        }),
25666    )
25667    .await;
25668
25669    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25670    let (workspace, cx) =
25671        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25672    let worktree_id = workspace.update(cx, |workspace, cx| {
25673        workspace.project().update(cx, |project, cx| {
25674            project.worktrees(cx).next().unwrap().read(cx).id()
25675        })
25676    });
25677
25678    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25679    let editor = workspace
25680        .update_in(cx, |workspace, window, cx| {
25681            workspace.open_path(
25682                (worktree_id, rel_path("main.rs")),
25683                Some(pane.downgrade()),
25684                true,
25685                window,
25686                cx,
25687            )
25688        })
25689        .unwrap()
25690        .await
25691        .downcast::<Editor>()
25692        .unwrap();
25693    pane.update(cx, |pane, cx| {
25694        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25695        open_editor.update(cx, |editor, cx| {
25696            assert_eq!(
25697                editor.display_text(cx),
25698                main_text,
25699                "Original main.rs text on initial open",
25700            );
25701        })
25702    });
25703    editor.update_in(cx, |editor, window, cx| {
25704        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25705    });
25706
25707    cx.update_global(|store: &mut SettingsStore, cx| {
25708        store.update_user_settings(cx, |s| {
25709            s.workspace.restore_on_file_reopen = Some(false);
25710        });
25711    });
25712    editor.update_in(cx, |editor, window, cx| {
25713        editor.fold_ranges(
25714            vec![
25715                Point::new(1, 0)..Point::new(1, 1),
25716                Point::new(2, 0)..Point::new(2, 2),
25717                Point::new(3, 0)..Point::new(3, 3),
25718            ],
25719            false,
25720            window,
25721            cx,
25722        );
25723    });
25724    pane.update_in(cx, |pane, window, cx| {
25725        pane.close_all_items(&CloseAllItems::default(), window, cx)
25726    })
25727    .await
25728    .unwrap();
25729    pane.update(cx, |pane, _| {
25730        assert!(pane.active_item().is_none());
25731    });
25732    cx.update_global(|store: &mut SettingsStore, cx| {
25733        store.update_user_settings(cx, |s| {
25734            s.workspace.restore_on_file_reopen = Some(true);
25735        });
25736    });
25737
25738    let _editor_reopened = workspace
25739        .update_in(cx, |workspace, window, cx| {
25740            workspace.open_path(
25741                (worktree_id, rel_path("main.rs")),
25742                Some(pane.downgrade()),
25743                true,
25744                window,
25745                cx,
25746            )
25747        })
25748        .unwrap()
25749        .await
25750        .downcast::<Editor>()
25751        .unwrap();
25752    pane.update(cx, |pane, cx| {
25753        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25754        open_editor.update(cx, |editor, cx| {
25755            assert_eq!(
25756                editor.display_text(cx),
25757                main_text,
25758                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25759            );
25760        })
25761    });
25762}
25763
25764#[gpui::test]
25765async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25766    struct EmptyModalView {
25767        focus_handle: gpui::FocusHandle,
25768    }
25769    impl EventEmitter<DismissEvent> for EmptyModalView {}
25770    impl Render for EmptyModalView {
25771        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25772            div()
25773        }
25774    }
25775    impl Focusable for EmptyModalView {
25776        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25777            self.focus_handle.clone()
25778        }
25779    }
25780    impl workspace::ModalView for EmptyModalView {}
25781    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25782        EmptyModalView {
25783            focus_handle: cx.focus_handle(),
25784        }
25785    }
25786
25787    init_test(cx, |_| {});
25788
25789    let fs = FakeFs::new(cx.executor());
25790    let project = Project::test(fs, [], cx).await;
25791    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25792    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25793    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25794    let editor = cx.new_window_entity(|window, cx| {
25795        Editor::new(
25796            EditorMode::full(),
25797            buffer,
25798            Some(project.clone()),
25799            window,
25800            cx,
25801        )
25802    });
25803    workspace
25804        .update(cx, |workspace, window, cx| {
25805            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25806        })
25807        .unwrap();
25808    editor.update_in(cx, |editor, window, cx| {
25809        editor.open_context_menu(&OpenContextMenu, window, cx);
25810        assert!(editor.mouse_context_menu.is_some());
25811    });
25812    workspace
25813        .update(cx, |workspace, window, cx| {
25814            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25815        })
25816        .unwrap();
25817    cx.read(|cx| {
25818        assert!(editor.read(cx).mouse_context_menu.is_none());
25819    });
25820}
25821
25822fn set_linked_edit_ranges(
25823    opening: (Point, Point),
25824    closing: (Point, Point),
25825    editor: &mut Editor,
25826    cx: &mut Context<Editor>,
25827) {
25828    let Some((buffer, _)) = editor
25829        .buffer
25830        .read(cx)
25831        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25832    else {
25833        panic!("Failed to get buffer for selection position");
25834    };
25835    let buffer = buffer.read(cx);
25836    let buffer_id = buffer.remote_id();
25837    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25838    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25839    let mut linked_ranges = HashMap::default();
25840    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25841    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25842}
25843
25844#[gpui::test]
25845async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25846    init_test(cx, |_| {});
25847
25848    let fs = FakeFs::new(cx.executor());
25849    fs.insert_file(path!("/file.html"), Default::default())
25850        .await;
25851
25852    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25853
25854    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25855    let html_language = Arc::new(Language::new(
25856        LanguageConfig {
25857            name: "HTML".into(),
25858            matcher: LanguageMatcher {
25859                path_suffixes: vec!["html".to_string()],
25860                ..LanguageMatcher::default()
25861            },
25862            brackets: BracketPairConfig {
25863                pairs: vec![BracketPair {
25864                    start: "<".into(),
25865                    end: ">".into(),
25866                    close: true,
25867                    ..Default::default()
25868                }],
25869                ..Default::default()
25870            },
25871            ..Default::default()
25872        },
25873        Some(tree_sitter_html::LANGUAGE.into()),
25874    ));
25875    language_registry.add(html_language);
25876    let mut fake_servers = language_registry.register_fake_lsp(
25877        "HTML",
25878        FakeLspAdapter {
25879            capabilities: lsp::ServerCapabilities {
25880                completion_provider: Some(lsp::CompletionOptions {
25881                    resolve_provider: Some(true),
25882                    ..Default::default()
25883                }),
25884                ..Default::default()
25885            },
25886            ..Default::default()
25887        },
25888    );
25889
25890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25891    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25892
25893    let worktree_id = workspace
25894        .update(cx, |workspace, _window, cx| {
25895            workspace.project().update(cx, |project, cx| {
25896                project.worktrees(cx).next().unwrap().read(cx).id()
25897            })
25898        })
25899        .unwrap();
25900    project
25901        .update(cx, |project, cx| {
25902            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25903        })
25904        .await
25905        .unwrap();
25906    let editor = workspace
25907        .update(cx, |workspace, window, cx| {
25908            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25909        })
25910        .unwrap()
25911        .await
25912        .unwrap()
25913        .downcast::<Editor>()
25914        .unwrap();
25915
25916    let fake_server = fake_servers.next().await.unwrap();
25917    cx.run_until_parked();
25918    editor.update_in(cx, |editor, window, cx| {
25919        editor.set_text("<ad></ad>", window, cx);
25920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25921            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25922        });
25923        set_linked_edit_ranges(
25924            (Point::new(0, 1), Point::new(0, 3)),
25925            (Point::new(0, 6), Point::new(0, 8)),
25926            editor,
25927            cx,
25928        );
25929    });
25930    let mut completion_handle =
25931        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25932            Ok(Some(lsp::CompletionResponse::Array(vec![
25933                lsp::CompletionItem {
25934                    label: "head".to_string(),
25935                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25936                        lsp::InsertReplaceEdit {
25937                            new_text: "head".to_string(),
25938                            insert: lsp::Range::new(
25939                                lsp::Position::new(0, 1),
25940                                lsp::Position::new(0, 3),
25941                            ),
25942                            replace: lsp::Range::new(
25943                                lsp::Position::new(0, 1),
25944                                lsp::Position::new(0, 3),
25945                            ),
25946                        },
25947                    )),
25948                    ..Default::default()
25949                },
25950            ])))
25951        });
25952    editor.update_in(cx, |editor, window, cx| {
25953        editor.show_completions(&ShowCompletions, window, cx);
25954    });
25955    cx.run_until_parked();
25956    completion_handle.next().await.unwrap();
25957    editor.update(cx, |editor, _| {
25958        assert!(
25959            editor.context_menu_visible(),
25960            "Completion menu should be visible"
25961        );
25962    });
25963    editor.update_in(cx, |editor, window, cx| {
25964        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25965    });
25966    cx.executor().run_until_parked();
25967    editor.update(cx, |editor, cx| {
25968        assert_eq!(editor.text(cx), "<head></head>");
25969    });
25970}
25971
25972#[gpui::test]
25973async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25974    init_test(cx, |_| {});
25975
25976    let mut cx = EditorTestContext::new(cx).await;
25977    let language = Arc::new(Language::new(
25978        LanguageConfig {
25979            name: "TSX".into(),
25980            matcher: LanguageMatcher {
25981                path_suffixes: vec!["tsx".to_string()],
25982                ..LanguageMatcher::default()
25983            },
25984            brackets: BracketPairConfig {
25985                pairs: vec![BracketPair {
25986                    start: "<".into(),
25987                    end: ">".into(),
25988                    close: true,
25989                    ..Default::default()
25990                }],
25991                ..Default::default()
25992            },
25993            linked_edit_characters: HashSet::from_iter(['.']),
25994            ..Default::default()
25995        },
25996        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25997    ));
25998    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25999
26000    // Test typing > does not extend linked pair
26001    cx.set_state("<divˇ<div></div>");
26002    cx.update_editor(|editor, _, cx| {
26003        set_linked_edit_ranges(
26004            (Point::new(0, 1), Point::new(0, 4)),
26005            (Point::new(0, 11), Point::new(0, 14)),
26006            editor,
26007            cx,
26008        );
26009    });
26010    cx.update_editor(|editor, window, cx| {
26011        editor.handle_input(">", window, cx);
26012    });
26013    cx.assert_editor_state("<div>ˇ<div></div>");
26014
26015    // Test typing . do extend linked pair
26016    cx.set_state("<Animatedˇ></Animated>");
26017    cx.update_editor(|editor, _, cx| {
26018        set_linked_edit_ranges(
26019            (Point::new(0, 1), Point::new(0, 9)),
26020            (Point::new(0, 12), Point::new(0, 20)),
26021            editor,
26022            cx,
26023        );
26024    });
26025    cx.update_editor(|editor, window, cx| {
26026        editor.handle_input(".", window, cx);
26027    });
26028    cx.assert_editor_state("<Animated.ˇ></Animated.>");
26029    cx.update_editor(|editor, _, cx| {
26030        set_linked_edit_ranges(
26031            (Point::new(0, 1), Point::new(0, 10)),
26032            (Point::new(0, 13), Point::new(0, 21)),
26033            editor,
26034            cx,
26035        );
26036    });
26037    cx.update_editor(|editor, window, cx| {
26038        editor.handle_input("V", window, cx);
26039    });
26040    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
26041}
26042
26043#[gpui::test]
26044async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
26045    init_test(cx, |_| {});
26046
26047    let fs = FakeFs::new(cx.executor());
26048    fs.insert_tree(
26049        path!("/root"),
26050        json!({
26051            "a": {
26052                "main.rs": "fn main() {}",
26053            },
26054            "foo": {
26055                "bar": {
26056                    "external_file.rs": "pub mod external {}",
26057                }
26058            }
26059        }),
26060    )
26061    .await;
26062
26063    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
26064    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26065    language_registry.add(rust_lang());
26066    let _fake_servers = language_registry.register_fake_lsp(
26067        "Rust",
26068        FakeLspAdapter {
26069            ..FakeLspAdapter::default()
26070        },
26071    );
26072    let (workspace, cx) =
26073        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26074    let worktree_id = workspace.update(cx, |workspace, cx| {
26075        workspace.project().update(cx, |project, cx| {
26076            project.worktrees(cx).next().unwrap().read(cx).id()
26077        })
26078    });
26079
26080    let assert_language_servers_count =
26081        |expected: usize, context: &str, cx: &mut VisualTestContext| {
26082            project.update(cx, |project, cx| {
26083                let current = project
26084                    .lsp_store()
26085                    .read(cx)
26086                    .as_local()
26087                    .unwrap()
26088                    .language_servers
26089                    .len();
26090                assert_eq!(expected, current, "{context}");
26091            });
26092        };
26093
26094    assert_language_servers_count(
26095        0,
26096        "No servers should be running before any file is open",
26097        cx,
26098    );
26099    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26100    let main_editor = workspace
26101        .update_in(cx, |workspace, window, cx| {
26102            workspace.open_path(
26103                (worktree_id, rel_path("main.rs")),
26104                Some(pane.downgrade()),
26105                true,
26106                window,
26107                cx,
26108            )
26109        })
26110        .unwrap()
26111        .await
26112        .downcast::<Editor>()
26113        .unwrap();
26114    pane.update(cx, |pane, cx| {
26115        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26116        open_editor.update(cx, |editor, cx| {
26117            assert_eq!(
26118                editor.display_text(cx),
26119                "fn main() {}",
26120                "Original main.rs text on initial open",
26121            );
26122        });
26123        assert_eq!(open_editor, main_editor);
26124    });
26125    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
26126
26127    let external_editor = workspace
26128        .update_in(cx, |workspace, window, cx| {
26129            workspace.open_abs_path(
26130                PathBuf::from("/root/foo/bar/external_file.rs"),
26131                OpenOptions::default(),
26132                window,
26133                cx,
26134            )
26135        })
26136        .await
26137        .expect("opening external file")
26138        .downcast::<Editor>()
26139        .expect("downcasted external file's open element to editor");
26140    pane.update(cx, |pane, cx| {
26141        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26142        open_editor.update(cx, |editor, cx| {
26143            assert_eq!(
26144                editor.display_text(cx),
26145                "pub mod external {}",
26146                "External file is open now",
26147            );
26148        });
26149        assert_eq!(open_editor, external_editor);
26150    });
26151    assert_language_servers_count(
26152        1,
26153        "Second, external, *.rs file should join the existing server",
26154        cx,
26155    );
26156
26157    pane.update_in(cx, |pane, window, cx| {
26158        pane.close_active_item(&CloseActiveItem::default(), window, cx)
26159    })
26160    .await
26161    .unwrap();
26162    pane.update_in(cx, |pane, window, cx| {
26163        pane.navigate_backward(&Default::default(), window, cx);
26164    });
26165    cx.run_until_parked();
26166    pane.update(cx, |pane, cx| {
26167        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26168        open_editor.update(cx, |editor, cx| {
26169            assert_eq!(
26170                editor.display_text(cx),
26171                "pub mod external {}",
26172                "External file is open now",
26173            );
26174        });
26175    });
26176    assert_language_servers_count(
26177        1,
26178        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
26179        cx,
26180    );
26181
26182    cx.update(|_, cx| {
26183        workspace::reload(cx);
26184    });
26185    assert_language_servers_count(
26186        1,
26187        "After reloading the worktree with local and external files opened, only one project should be started",
26188        cx,
26189    );
26190}
26191
26192#[gpui::test]
26193async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
26194    init_test(cx, |_| {});
26195
26196    let mut cx = EditorTestContext::new(cx).await;
26197    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26198    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26199
26200    // test cursor move to start of each line on tab
26201    // for `if`, `elif`, `else`, `while`, `with` and `for`
26202    cx.set_state(indoc! {"
26203        def main():
26204        ˇ    for item in items:
26205        ˇ        while item.active:
26206        ˇ            if item.value > 10:
26207        ˇ                continue
26208        ˇ            elif item.value < 0:
26209        ˇ                break
26210        ˇ            else:
26211        ˇ                with item.context() as ctx:
26212        ˇ                    yield count
26213        ˇ        else:
26214        ˇ            log('while else')
26215        ˇ    else:
26216        ˇ        log('for else')
26217    "});
26218    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26219    cx.wait_for_autoindent_applied().await;
26220    cx.assert_editor_state(indoc! {"
26221        def main():
26222            ˇfor item in items:
26223                ˇwhile item.active:
26224                    ˇif item.value > 10:
26225                        ˇcontinue
26226                    ˇelif item.value < 0:
26227                        ˇbreak
26228                    ˇelse:
26229                        ˇwith item.context() as ctx:
26230                            ˇyield count
26231                ˇelse:
26232                    ˇlog('while else')
26233            ˇelse:
26234                ˇlog('for else')
26235    "});
26236    // test relative indent is preserved when tab
26237    // for `if`, `elif`, `else`, `while`, `with` and `for`
26238    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26239    cx.wait_for_autoindent_applied().await;
26240    cx.assert_editor_state(indoc! {"
26241        def main():
26242                ˇfor item in items:
26243                    ˇwhile item.active:
26244                        ˇif item.value > 10:
26245                            ˇcontinue
26246                        ˇelif item.value < 0:
26247                            ˇbreak
26248                        ˇelse:
26249                            ˇwith item.context() as ctx:
26250                                ˇyield count
26251                    ˇelse:
26252                        ˇlog('while else')
26253                ˇelse:
26254                    ˇlog('for else')
26255    "});
26256
26257    // test cursor move to start of each line on tab
26258    // for `try`, `except`, `else`, `finally`, `match` and `def`
26259    cx.set_state(indoc! {"
26260        def main():
26261        ˇ    try:
26262        ˇ        fetch()
26263        ˇ    except ValueError:
26264        ˇ        handle_error()
26265        ˇ    else:
26266        ˇ        match value:
26267        ˇ            case _:
26268        ˇ    finally:
26269        ˇ        def status():
26270        ˇ            return 0
26271    "});
26272    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26273    cx.wait_for_autoindent_applied().await;
26274    cx.assert_editor_state(indoc! {"
26275        def main():
26276            ˇtry:
26277                ˇfetch()
26278            ˇexcept ValueError:
26279                ˇhandle_error()
26280            ˇelse:
26281                ˇmatch value:
26282                    ˇcase _:
26283            ˇfinally:
26284                ˇdef status():
26285                    ˇreturn 0
26286    "});
26287    // test relative indent is preserved when tab
26288    // for `try`, `except`, `else`, `finally`, `match` and `def`
26289    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26290    cx.wait_for_autoindent_applied().await;
26291    cx.assert_editor_state(indoc! {"
26292        def main():
26293                ˇtry:
26294                    ˇfetch()
26295                ˇexcept ValueError:
26296                    ˇhandle_error()
26297                ˇelse:
26298                    ˇmatch value:
26299                        ˇcase _:
26300                ˇfinally:
26301                    ˇdef status():
26302                        ˇreturn 0
26303    "});
26304}
26305
26306#[gpui::test]
26307async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
26308    init_test(cx, |_| {});
26309
26310    let mut cx = EditorTestContext::new(cx).await;
26311    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26312    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26313
26314    // test `else` auto outdents when typed inside `if` block
26315    cx.set_state(indoc! {"
26316        def main():
26317            if i == 2:
26318                return
26319                ˇ
26320    "});
26321    cx.update_editor(|editor, window, cx| {
26322        editor.handle_input("else:", window, cx);
26323    });
26324    cx.wait_for_autoindent_applied().await;
26325    cx.assert_editor_state(indoc! {"
26326        def main():
26327            if i == 2:
26328                return
26329            else:ˇ
26330    "});
26331
26332    // test `except` auto outdents when typed inside `try` block
26333    cx.set_state(indoc! {"
26334        def main():
26335            try:
26336                i = 2
26337                ˇ
26338    "});
26339    cx.update_editor(|editor, window, cx| {
26340        editor.handle_input("except:", window, cx);
26341    });
26342    cx.wait_for_autoindent_applied().await;
26343    cx.assert_editor_state(indoc! {"
26344        def main():
26345            try:
26346                i = 2
26347            except:ˇ
26348    "});
26349
26350    // test `else` auto outdents when typed inside `except` block
26351    cx.set_state(indoc! {"
26352        def main():
26353            try:
26354                i = 2
26355            except:
26356                j = 2
26357                ˇ
26358    "});
26359    cx.update_editor(|editor, window, cx| {
26360        editor.handle_input("else:", window, cx);
26361    });
26362    cx.wait_for_autoindent_applied().await;
26363    cx.assert_editor_state(indoc! {"
26364        def main():
26365            try:
26366                i = 2
26367            except:
26368                j = 2
26369            else:ˇ
26370    "});
26371
26372    // test `finally` auto outdents when typed inside `else` block
26373    cx.set_state(indoc! {"
26374        def main():
26375            try:
26376                i = 2
26377            except:
26378                j = 2
26379            else:
26380                k = 2
26381                ˇ
26382    "});
26383    cx.update_editor(|editor, window, cx| {
26384        editor.handle_input("finally:", window, cx);
26385    });
26386    cx.wait_for_autoindent_applied().await;
26387    cx.assert_editor_state(indoc! {"
26388        def main():
26389            try:
26390                i = 2
26391            except:
26392                j = 2
26393            else:
26394                k = 2
26395            finally:ˇ
26396    "});
26397
26398    // test `else` does not outdents when typed inside `except` block right after for block
26399    cx.set_state(indoc! {"
26400        def main():
26401            try:
26402                i = 2
26403            except:
26404                for i in range(n):
26405                    pass
26406                ˇ
26407    "});
26408    cx.update_editor(|editor, window, cx| {
26409        editor.handle_input("else:", window, cx);
26410    });
26411    cx.wait_for_autoindent_applied().await;
26412    cx.assert_editor_state(indoc! {"
26413        def main():
26414            try:
26415                i = 2
26416            except:
26417                for i in range(n):
26418                    pass
26419                else:ˇ
26420    "});
26421
26422    // test `finally` auto outdents when typed inside `else` block right after for block
26423    cx.set_state(indoc! {"
26424        def main():
26425            try:
26426                i = 2
26427            except:
26428                j = 2
26429            else:
26430                for i in range(n):
26431                    pass
26432                ˇ
26433    "});
26434    cx.update_editor(|editor, window, cx| {
26435        editor.handle_input("finally:", window, cx);
26436    });
26437    cx.wait_for_autoindent_applied().await;
26438    cx.assert_editor_state(indoc! {"
26439        def main():
26440            try:
26441                i = 2
26442            except:
26443                j = 2
26444            else:
26445                for i in range(n):
26446                    pass
26447            finally:ˇ
26448    "});
26449
26450    // test `except` outdents to inner "try" block
26451    cx.set_state(indoc! {"
26452        def main():
26453            try:
26454                i = 2
26455                if i == 2:
26456                    try:
26457                        i = 3
26458                        ˇ
26459    "});
26460    cx.update_editor(|editor, window, cx| {
26461        editor.handle_input("except:", window, cx);
26462    });
26463    cx.wait_for_autoindent_applied().await;
26464    cx.assert_editor_state(indoc! {"
26465        def main():
26466            try:
26467                i = 2
26468                if i == 2:
26469                    try:
26470                        i = 3
26471                    except:ˇ
26472    "});
26473
26474    // test `except` outdents to outer "try" block
26475    cx.set_state(indoc! {"
26476        def main():
26477            try:
26478                i = 2
26479                if i == 2:
26480                    try:
26481                        i = 3
26482                ˇ
26483    "});
26484    cx.update_editor(|editor, window, cx| {
26485        editor.handle_input("except:", window, cx);
26486    });
26487    cx.wait_for_autoindent_applied().await;
26488    cx.assert_editor_state(indoc! {"
26489        def main():
26490            try:
26491                i = 2
26492                if i == 2:
26493                    try:
26494                        i = 3
26495            except:ˇ
26496    "});
26497
26498    // test `else` stays at correct indent when typed after `for` block
26499    cx.set_state(indoc! {"
26500        def main():
26501            for i in range(10):
26502                if i == 3:
26503                    break
26504            ˇ
26505    "});
26506    cx.update_editor(|editor, window, cx| {
26507        editor.handle_input("else:", window, cx);
26508    });
26509    cx.wait_for_autoindent_applied().await;
26510    cx.assert_editor_state(indoc! {"
26511        def main():
26512            for i in range(10):
26513                if i == 3:
26514                    break
26515            else:ˇ
26516    "});
26517
26518    // test does not outdent on typing after line with square brackets
26519    cx.set_state(indoc! {"
26520        def f() -> list[str]:
26521            ˇ
26522    "});
26523    cx.update_editor(|editor, window, cx| {
26524        editor.handle_input("a", window, cx);
26525    });
26526    cx.wait_for_autoindent_applied().await;
26527    cx.assert_editor_state(indoc! {"
26528        def f() -> list[str]:
2652926530    "});
26531
26532    // test does not outdent on typing : after case keyword
26533    cx.set_state(indoc! {"
26534        match 1:
26535            caseˇ
26536    "});
26537    cx.update_editor(|editor, window, cx| {
26538        editor.handle_input(":", window, cx);
26539    });
26540    cx.wait_for_autoindent_applied().await;
26541    cx.assert_editor_state(indoc! {"
26542        match 1:
26543            case:ˇ
26544    "});
26545}
26546
26547#[gpui::test]
26548async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26549    init_test(cx, |_| {});
26550    update_test_language_settings(cx, |settings| {
26551        settings.defaults.extend_comment_on_newline = Some(false);
26552    });
26553    let mut cx = EditorTestContext::new(cx).await;
26554    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26555    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26556
26557    // test correct indent after newline on comment
26558    cx.set_state(indoc! {"
26559        # COMMENT:ˇ
26560    "});
26561    cx.update_editor(|editor, window, cx| {
26562        editor.newline(&Newline, window, cx);
26563    });
26564    cx.wait_for_autoindent_applied().await;
26565    cx.assert_editor_state(indoc! {"
26566        # COMMENT:
26567        ˇ
26568    "});
26569
26570    // test correct indent after newline in brackets
26571    cx.set_state(indoc! {"
26572        {ˇ}
26573    "});
26574    cx.update_editor(|editor, window, cx| {
26575        editor.newline(&Newline, window, cx);
26576    });
26577    cx.wait_for_autoindent_applied().await;
26578    cx.assert_editor_state(indoc! {"
26579        {
26580            ˇ
26581        }
26582    "});
26583
26584    cx.set_state(indoc! {"
26585        (ˇ)
26586    "});
26587    cx.update_editor(|editor, window, cx| {
26588        editor.newline(&Newline, window, cx);
26589    });
26590    cx.run_until_parked();
26591    cx.assert_editor_state(indoc! {"
26592        (
26593            ˇ
26594        )
26595    "});
26596
26597    // do not indent after empty lists or dictionaries
26598    cx.set_state(indoc! {"
26599        a = []ˇ
26600    "});
26601    cx.update_editor(|editor, window, cx| {
26602        editor.newline(&Newline, window, cx);
26603    });
26604    cx.run_until_parked();
26605    cx.assert_editor_state(indoc! {"
26606        a = []
26607        ˇ
26608    "});
26609}
26610
26611#[gpui::test]
26612async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26613    init_test(cx, |_| {});
26614
26615    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26616    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26617    language_registry.add(markdown_lang());
26618    language_registry.add(python_lang);
26619
26620    let mut cx = EditorTestContext::new(cx).await;
26621    cx.update_buffer(|buffer, cx| {
26622        buffer.set_language_registry(language_registry);
26623        buffer.set_language(Some(markdown_lang()), cx);
26624    });
26625
26626    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26627    cx.set_state(indoc! {"
26628        # Heading
26629
26630        ```python
26631        def main():
26632            if condition:
26633                pass
26634                ˇ
26635        ```
26636    "});
26637    cx.update_editor(|editor, window, cx| {
26638        editor.handle_input("else:", window, cx);
26639    });
26640    cx.run_until_parked();
26641    cx.assert_editor_state(indoc! {"
26642        # Heading
26643
26644        ```python
26645        def main():
26646            if condition:
26647                pass
26648            else:ˇ
26649        ```
26650    "});
26651}
26652
26653#[gpui::test]
26654async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26655    init_test(cx, |_| {});
26656
26657    let mut cx = EditorTestContext::new(cx).await;
26658    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26659    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26660
26661    // test cursor move to start of each line on tab
26662    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26663    cx.set_state(indoc! {"
26664        function main() {
26665        ˇ    for item in $items; do
26666        ˇ        while [ -n \"$item\" ]; do
26667        ˇ            if [ \"$value\" -gt 10 ]; then
26668        ˇ                continue
26669        ˇ            elif [ \"$value\" -lt 0 ]; then
26670        ˇ                break
26671        ˇ            else
26672        ˇ                echo \"$item\"
26673        ˇ            fi
26674        ˇ        done
26675        ˇ    done
26676        ˇ}
26677    "});
26678    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26679    cx.wait_for_autoindent_applied().await;
26680    cx.assert_editor_state(indoc! {"
26681        function main() {
26682            ˇfor item in $items; do
26683                ˇwhile [ -n \"$item\" ]; do
26684                    ˇif [ \"$value\" -gt 10 ]; then
26685                        ˇcontinue
26686                    ˇelif [ \"$value\" -lt 0 ]; then
26687                        ˇbreak
26688                    ˇelse
26689                        ˇecho \"$item\"
26690                    ˇfi
26691                ˇdone
26692            ˇdone
26693        ˇ}
26694    "});
26695    // test relative indent is preserved when tab
26696    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26697    cx.wait_for_autoindent_applied().await;
26698    cx.assert_editor_state(indoc! {"
26699        function main() {
26700                ˇfor item in $items; do
26701                    ˇwhile [ -n \"$item\" ]; do
26702                        ˇif [ \"$value\" -gt 10 ]; then
26703                            ˇcontinue
26704                        ˇelif [ \"$value\" -lt 0 ]; then
26705                            ˇbreak
26706                        ˇelse
26707                            ˇecho \"$item\"
26708                        ˇfi
26709                    ˇdone
26710                ˇdone
26711            ˇ}
26712    "});
26713
26714    // test cursor move to start of each line on tab
26715    // for `case` statement with patterns
26716    cx.set_state(indoc! {"
26717        function handle() {
26718        ˇ    case \"$1\" in
26719        ˇ        start)
26720        ˇ            echo \"a\"
26721        ˇ            ;;
26722        ˇ        stop)
26723        ˇ            echo \"b\"
26724        ˇ            ;;
26725        ˇ        *)
26726        ˇ            echo \"c\"
26727        ˇ            ;;
26728        ˇ    esac
26729        ˇ}
26730    "});
26731    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26732    cx.wait_for_autoindent_applied().await;
26733    cx.assert_editor_state(indoc! {"
26734        function handle() {
26735            ˇcase \"$1\" in
26736                ˇstart)
26737                    ˇecho \"a\"
26738                    ˇ;;
26739                ˇstop)
26740                    ˇecho \"b\"
26741                    ˇ;;
26742                ˇ*)
26743                    ˇecho \"c\"
26744                    ˇ;;
26745            ˇesac
26746        ˇ}
26747    "});
26748}
26749
26750#[gpui::test]
26751async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26752    init_test(cx, |_| {});
26753
26754    let mut cx = EditorTestContext::new(cx).await;
26755    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26756    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26757
26758    // test indents on comment insert
26759    cx.set_state(indoc! {"
26760        function main() {
26761        ˇ    for item in $items; do
26762        ˇ        while [ -n \"$item\" ]; do
26763        ˇ            if [ \"$value\" -gt 10 ]; then
26764        ˇ                continue
26765        ˇ            elif [ \"$value\" -lt 0 ]; then
26766        ˇ                break
26767        ˇ            else
26768        ˇ                echo \"$item\"
26769        ˇ            fi
26770        ˇ        done
26771        ˇ    done
26772        ˇ}
26773    "});
26774    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26775    cx.wait_for_autoindent_applied().await;
26776    cx.assert_editor_state(indoc! {"
26777        function main() {
26778        #ˇ    for item in $items; do
26779        #ˇ        while [ -n \"$item\" ]; do
26780        #ˇ            if [ \"$value\" -gt 10 ]; then
26781        #ˇ                continue
26782        #ˇ            elif [ \"$value\" -lt 0 ]; then
26783        #ˇ                break
26784        #ˇ            else
26785        #ˇ                echo \"$item\"
26786        #ˇ            fi
26787        #ˇ        done
26788        #ˇ    done
26789        #ˇ}
26790    "});
26791}
26792
26793#[gpui::test]
26794async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26795    init_test(cx, |_| {});
26796
26797    let mut cx = EditorTestContext::new(cx).await;
26798    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26799    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26800
26801    // test `else` auto outdents when typed inside `if` block
26802    cx.set_state(indoc! {"
26803        if [ \"$1\" = \"test\" ]; then
26804            echo \"foo bar\"
26805            ˇ
26806    "});
26807    cx.update_editor(|editor, window, cx| {
26808        editor.handle_input("else", window, cx);
26809    });
26810    cx.wait_for_autoindent_applied().await;
26811    cx.assert_editor_state(indoc! {"
26812        if [ \"$1\" = \"test\" ]; then
26813            echo \"foo bar\"
26814        elseˇ
26815    "});
26816
26817    // test `elif` auto outdents when typed inside `if` block
26818    cx.set_state(indoc! {"
26819        if [ \"$1\" = \"test\" ]; then
26820            echo \"foo bar\"
26821            ˇ
26822    "});
26823    cx.update_editor(|editor, window, cx| {
26824        editor.handle_input("elif", window, cx);
26825    });
26826    cx.wait_for_autoindent_applied().await;
26827    cx.assert_editor_state(indoc! {"
26828        if [ \"$1\" = \"test\" ]; then
26829            echo \"foo bar\"
26830        elifˇ
26831    "});
26832
26833    // test `fi` auto outdents when typed inside `else` block
26834    cx.set_state(indoc! {"
26835        if [ \"$1\" = \"test\" ]; then
26836            echo \"foo bar\"
26837        else
26838            echo \"bar baz\"
26839            ˇ
26840    "});
26841    cx.update_editor(|editor, window, cx| {
26842        editor.handle_input("fi", window, cx);
26843    });
26844    cx.wait_for_autoindent_applied().await;
26845    cx.assert_editor_state(indoc! {"
26846        if [ \"$1\" = \"test\" ]; then
26847            echo \"foo bar\"
26848        else
26849            echo \"bar baz\"
26850        fiˇ
26851    "});
26852
26853    // test `done` auto outdents when typed inside `while` block
26854    cx.set_state(indoc! {"
26855        while read line; do
26856            echo \"$line\"
26857            ˇ
26858    "});
26859    cx.update_editor(|editor, window, cx| {
26860        editor.handle_input("done", window, cx);
26861    });
26862    cx.wait_for_autoindent_applied().await;
26863    cx.assert_editor_state(indoc! {"
26864        while read line; do
26865            echo \"$line\"
26866        doneˇ
26867    "});
26868
26869    // test `done` auto outdents when typed inside `for` block
26870    cx.set_state(indoc! {"
26871        for file in *.txt; do
26872            cat \"$file\"
26873            ˇ
26874    "});
26875    cx.update_editor(|editor, window, cx| {
26876        editor.handle_input("done", window, cx);
26877    });
26878    cx.wait_for_autoindent_applied().await;
26879    cx.assert_editor_state(indoc! {"
26880        for file in *.txt; do
26881            cat \"$file\"
26882        doneˇ
26883    "});
26884
26885    // test `esac` auto outdents when typed inside `case` block
26886    cx.set_state(indoc! {"
26887        case \"$1\" in
26888            start)
26889                echo \"foo bar\"
26890                ;;
26891            stop)
26892                echo \"bar baz\"
26893                ;;
26894            ˇ
26895    "});
26896    cx.update_editor(|editor, window, cx| {
26897        editor.handle_input("esac", window, cx);
26898    });
26899    cx.wait_for_autoindent_applied().await;
26900    cx.assert_editor_state(indoc! {"
26901        case \"$1\" in
26902            start)
26903                echo \"foo bar\"
26904                ;;
26905            stop)
26906                echo \"bar baz\"
26907                ;;
26908        esacˇ
26909    "});
26910
26911    // test `*)` auto outdents when typed inside `case` block
26912    cx.set_state(indoc! {"
26913        case \"$1\" in
26914            start)
26915                echo \"foo bar\"
26916                ;;
26917                ˇ
26918    "});
26919    cx.update_editor(|editor, window, cx| {
26920        editor.handle_input("*)", window, cx);
26921    });
26922    cx.wait_for_autoindent_applied().await;
26923    cx.assert_editor_state(indoc! {"
26924        case \"$1\" in
26925            start)
26926                echo \"foo bar\"
26927                ;;
26928            *)ˇ
26929    "});
26930
26931    // test `fi` outdents to correct level with nested if blocks
26932    cx.set_state(indoc! {"
26933        if [ \"$1\" = \"test\" ]; then
26934            echo \"outer if\"
26935            if [ \"$2\" = \"debug\" ]; then
26936                echo \"inner if\"
26937                ˇ
26938    "});
26939    cx.update_editor(|editor, window, cx| {
26940        editor.handle_input("fi", window, cx);
26941    });
26942    cx.wait_for_autoindent_applied().await;
26943    cx.assert_editor_state(indoc! {"
26944        if [ \"$1\" = \"test\" ]; then
26945            echo \"outer if\"
26946            if [ \"$2\" = \"debug\" ]; then
26947                echo \"inner if\"
26948            fiˇ
26949    "});
26950}
26951
26952#[gpui::test]
26953async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26954    init_test(cx, |_| {});
26955    update_test_language_settings(cx, |settings| {
26956        settings.defaults.extend_comment_on_newline = Some(false);
26957    });
26958    let mut cx = EditorTestContext::new(cx).await;
26959    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26960    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26961
26962    // test correct indent after newline on comment
26963    cx.set_state(indoc! {"
26964        # COMMENT:ˇ
26965    "});
26966    cx.update_editor(|editor, window, cx| {
26967        editor.newline(&Newline, window, cx);
26968    });
26969    cx.wait_for_autoindent_applied().await;
26970    cx.assert_editor_state(indoc! {"
26971        # COMMENT:
26972        ˇ
26973    "});
26974
26975    // test correct indent after newline after `then`
26976    cx.set_state(indoc! {"
26977
26978        if [ \"$1\" = \"test\" ]; thenˇ
26979    "});
26980    cx.update_editor(|editor, window, cx| {
26981        editor.newline(&Newline, window, cx);
26982    });
26983    cx.wait_for_autoindent_applied().await;
26984    cx.assert_editor_state(indoc! {"
26985
26986        if [ \"$1\" = \"test\" ]; then
26987            ˇ
26988    "});
26989
26990    // test correct indent after newline after `else`
26991    cx.set_state(indoc! {"
26992        if [ \"$1\" = \"test\" ]; then
26993        elseˇ
26994    "});
26995    cx.update_editor(|editor, window, cx| {
26996        editor.newline(&Newline, window, cx);
26997    });
26998    cx.wait_for_autoindent_applied().await;
26999    cx.assert_editor_state(indoc! {"
27000        if [ \"$1\" = \"test\" ]; then
27001        else
27002            ˇ
27003    "});
27004
27005    // test correct indent after newline after `elif`
27006    cx.set_state(indoc! {"
27007        if [ \"$1\" = \"test\" ]; then
27008        elifˇ
27009    "});
27010    cx.update_editor(|editor, window, cx| {
27011        editor.newline(&Newline, window, cx);
27012    });
27013    cx.wait_for_autoindent_applied().await;
27014    cx.assert_editor_state(indoc! {"
27015        if [ \"$1\" = \"test\" ]; then
27016        elif
27017            ˇ
27018    "});
27019
27020    // test correct indent after newline after `do`
27021    cx.set_state(indoc! {"
27022        for file in *.txt; doˇ
27023    "});
27024    cx.update_editor(|editor, window, cx| {
27025        editor.newline(&Newline, window, cx);
27026    });
27027    cx.wait_for_autoindent_applied().await;
27028    cx.assert_editor_state(indoc! {"
27029        for file in *.txt; do
27030            ˇ
27031    "});
27032
27033    // test correct indent after newline after case pattern
27034    cx.set_state(indoc! {"
27035        case \"$1\" in
27036            start)ˇ
27037    "});
27038    cx.update_editor(|editor, window, cx| {
27039        editor.newline(&Newline, window, cx);
27040    });
27041    cx.wait_for_autoindent_applied().await;
27042    cx.assert_editor_state(indoc! {"
27043        case \"$1\" in
27044            start)
27045                ˇ
27046    "});
27047
27048    // test correct indent after newline after case pattern
27049    cx.set_state(indoc! {"
27050        case \"$1\" in
27051            start)
27052                ;;
27053            *)ˇ
27054    "});
27055    cx.update_editor(|editor, window, cx| {
27056        editor.newline(&Newline, window, cx);
27057    });
27058    cx.wait_for_autoindent_applied().await;
27059    cx.assert_editor_state(indoc! {"
27060        case \"$1\" in
27061            start)
27062                ;;
27063            *)
27064                ˇ
27065    "});
27066
27067    // test correct indent after newline after function opening brace
27068    cx.set_state(indoc! {"
27069        function test() {ˇ}
27070    "});
27071    cx.update_editor(|editor, window, cx| {
27072        editor.newline(&Newline, window, cx);
27073    });
27074    cx.wait_for_autoindent_applied().await;
27075    cx.assert_editor_state(indoc! {"
27076        function test() {
27077            ˇ
27078        }
27079    "});
27080
27081    // test no extra indent after semicolon on same line
27082    cx.set_state(indoc! {"
27083        echo \"test\"27084    "});
27085    cx.update_editor(|editor, window, cx| {
27086        editor.newline(&Newline, window, cx);
27087    });
27088    cx.wait_for_autoindent_applied().await;
27089    cx.assert_editor_state(indoc! {"
27090        echo \"test\";
27091        ˇ
27092    "});
27093}
27094
27095fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
27096    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
27097    point..point
27098}
27099
27100#[track_caller]
27101fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
27102    let (text, ranges) = marked_text_ranges(marked_text, true);
27103    assert_eq!(editor.text(cx), text);
27104    assert_eq!(
27105        editor.selections.ranges(&editor.display_snapshot(cx)),
27106        ranges
27107            .iter()
27108            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
27109            .collect::<Vec<_>>(),
27110        "Assert selections are {}",
27111        marked_text
27112    );
27113}
27114
27115pub fn handle_signature_help_request(
27116    cx: &mut EditorLspTestContext,
27117    mocked_response: lsp::SignatureHelp,
27118) -> impl Future<Output = ()> + use<> {
27119    let mut request =
27120        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
27121            let mocked_response = mocked_response.clone();
27122            async move { Ok(Some(mocked_response)) }
27123        });
27124
27125    async move {
27126        request.next().await;
27127    }
27128}
27129
27130#[track_caller]
27131pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
27132    cx.update_editor(|editor, _, _| {
27133        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
27134            let entries = menu.entries.borrow();
27135            let entries = entries
27136                .iter()
27137                .map(|entry| entry.string.as_str())
27138                .collect::<Vec<_>>();
27139            assert_eq!(entries, expected);
27140        } else {
27141            panic!("Expected completions menu");
27142        }
27143    });
27144}
27145
27146#[gpui::test]
27147async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
27148    init_test(cx, |_| {});
27149    let mut cx = EditorLspTestContext::new_rust(
27150        lsp::ServerCapabilities {
27151            completion_provider: Some(lsp::CompletionOptions {
27152                ..Default::default()
27153            }),
27154            ..Default::default()
27155        },
27156        cx,
27157    )
27158    .await;
27159    cx.lsp
27160        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
27161            Ok(Some(lsp::CompletionResponse::Array(vec![
27162                lsp::CompletionItem {
27163                    label: "unsafe".into(),
27164                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27165                        range: lsp::Range {
27166                            start: lsp::Position {
27167                                line: 0,
27168                                character: 9,
27169                            },
27170                            end: lsp::Position {
27171                                line: 0,
27172                                character: 11,
27173                            },
27174                        },
27175                        new_text: "unsafe".to_string(),
27176                    })),
27177                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
27178                    ..Default::default()
27179                },
27180            ])))
27181        });
27182
27183    cx.update_editor(|editor, _, cx| {
27184        editor.project().unwrap().update(cx, |project, cx| {
27185            project.snippets().update(cx, |snippets, _cx| {
27186                snippets.add_snippet_for_test(
27187                    None,
27188                    PathBuf::from("test_snippets.json"),
27189                    vec![
27190                        Arc::new(project::snippet_provider::Snippet {
27191                            prefix: vec![
27192                                "unlimited word count".to_string(),
27193                                "unlimit word count".to_string(),
27194                                "unlimited unknown".to_string(),
27195                            ],
27196                            body: "this is many words".to_string(),
27197                            description: Some("description".to_string()),
27198                            name: "multi-word snippet test".to_string(),
27199                        }),
27200                        Arc::new(project::snippet_provider::Snippet {
27201                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
27202                            body: "fewer words".to_string(),
27203                            description: Some("alt description".to_string()),
27204                            name: "other name".to_string(),
27205                        }),
27206                        Arc::new(project::snippet_provider::Snippet {
27207                            prefix: vec!["ab aa".to_string()],
27208                            body: "abcd".to_string(),
27209                            description: None,
27210                            name: "alphabet".to_string(),
27211                        }),
27212                    ],
27213                );
27214            });
27215        })
27216    });
27217
27218    let get_completions = |cx: &mut EditorLspTestContext| {
27219        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
27220            Some(CodeContextMenu::Completions(context_menu)) => {
27221                let entries = context_menu.entries.borrow();
27222                entries
27223                    .iter()
27224                    .map(|entry| entry.string.clone())
27225                    .collect_vec()
27226            }
27227            _ => vec![],
27228        })
27229    };
27230
27231    // snippets:
27232    //  @foo
27233    //  foo bar
27234    //
27235    // when typing:
27236    //
27237    // when typing:
27238    //  - if I type a symbol "open the completions with snippets only"
27239    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
27240    //
27241    // stuff we need:
27242    //  - filtering logic change?
27243    //  - remember how far back the completion started.
27244
27245    let test_cases: &[(&str, &[&str])] = &[
27246        (
27247            "un",
27248            &[
27249                "unsafe",
27250                "unlimit word count",
27251                "unlimited unknown",
27252                "unlimited word count",
27253                "unsnip",
27254            ],
27255        ),
27256        (
27257            "u ",
27258            &[
27259                "unlimit word count",
27260                "unlimited unknown",
27261                "unlimited word count",
27262            ],
27263        ),
27264        ("u a", &["ab aa", "unsafe"]), // unsAfe
27265        (
27266            "u u",
27267            &[
27268                "unsafe",
27269                "unlimit word count",
27270                "unlimited unknown", // ranked highest among snippets
27271                "unlimited word count",
27272                "unsnip",
27273            ],
27274        ),
27275        ("uw c", &["unlimit word count", "unlimited word count"]),
27276        (
27277            "u w",
27278            &[
27279                "unlimit word count",
27280                "unlimited word count",
27281                "unlimited unknown",
27282            ],
27283        ),
27284        ("u w ", &["unlimit word count", "unlimited word count"]),
27285        (
27286            "u ",
27287            &[
27288                "unlimit word count",
27289                "unlimited unknown",
27290                "unlimited word count",
27291            ],
27292        ),
27293        ("wor", &[]),
27294        ("uf", &["unsafe"]),
27295        ("af", &["unsafe"]),
27296        ("afu", &[]),
27297        (
27298            "ue",
27299            &["unsafe", "unlimited unknown", "unlimited word count"],
27300        ),
27301        ("@", &["@few"]),
27302        ("@few", &["@few"]),
27303        ("@ ", &[]),
27304        ("a@", &["@few"]),
27305        ("a@f", &["@few", "unsafe"]),
27306        ("a@fw", &["@few"]),
27307        ("a", &["ab aa", "unsafe"]),
27308        ("aa", &["ab aa"]),
27309        ("aaa", &["ab aa"]),
27310        ("ab", &["ab aa"]),
27311        ("ab ", &["ab aa"]),
27312        ("ab a", &["ab aa", "unsafe"]),
27313        ("ab ab", &["ab aa"]),
27314        ("ab ab aa", &["ab aa"]),
27315    ];
27316
27317    for &(input_to_simulate, expected_completions) in test_cases {
27318        cx.set_state("fn a() { ˇ }\n");
27319        for c in input_to_simulate.split("") {
27320            cx.simulate_input(c);
27321            cx.run_until_parked();
27322        }
27323        let expected_completions = expected_completions
27324            .iter()
27325            .map(|s| s.to_string())
27326            .collect_vec();
27327        assert_eq!(
27328            get_completions(&mut cx),
27329            expected_completions,
27330            "< actual / expected >, input = {input_to_simulate:?}",
27331        );
27332    }
27333}
27334
27335/// Handle completion request passing a marked string specifying where the completion
27336/// should be triggered from using '|' character, what range should be replaced, and what completions
27337/// should be returned using '<' and '>' to delimit the range.
27338///
27339/// Also see `handle_completion_request_with_insert_and_replace`.
27340#[track_caller]
27341pub fn handle_completion_request(
27342    marked_string: &str,
27343    completions: Vec<&'static str>,
27344    is_incomplete: bool,
27345    counter: Arc<AtomicUsize>,
27346    cx: &mut EditorLspTestContext,
27347) -> impl Future<Output = ()> {
27348    let complete_from_marker: TextRangeMarker = '|'.into();
27349    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27350    let (_, mut marked_ranges) = marked_text_ranges_by(
27351        marked_string,
27352        vec![complete_from_marker.clone(), replace_range_marker.clone()],
27353    );
27354
27355    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27356        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27357    ));
27358    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27359    let replace_range =
27360        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27361
27362    let mut request =
27363        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27364            let completions = completions.clone();
27365            counter.fetch_add(1, atomic::Ordering::Release);
27366            async move {
27367                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27368                assert_eq!(
27369                    params.text_document_position.position,
27370                    complete_from_position
27371                );
27372                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
27373                    is_incomplete,
27374                    item_defaults: None,
27375                    items: completions
27376                        .iter()
27377                        .map(|completion_text| lsp::CompletionItem {
27378                            label: completion_text.to_string(),
27379                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27380                                range: replace_range,
27381                                new_text: completion_text.to_string(),
27382                            })),
27383                            ..Default::default()
27384                        })
27385                        .collect(),
27386                })))
27387            }
27388        });
27389
27390    async move {
27391        request.next().await;
27392    }
27393}
27394
27395/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27396/// given instead, which also contains an `insert` range.
27397///
27398/// This function uses markers to define ranges:
27399/// - `|` marks the cursor position
27400/// - `<>` marks the replace range
27401/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27402pub fn handle_completion_request_with_insert_and_replace(
27403    cx: &mut EditorLspTestContext,
27404    marked_string: &str,
27405    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27406    counter: Arc<AtomicUsize>,
27407) -> impl Future<Output = ()> {
27408    let complete_from_marker: TextRangeMarker = '|'.into();
27409    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27410    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27411
27412    let (_, mut marked_ranges) = marked_text_ranges_by(
27413        marked_string,
27414        vec![
27415            complete_from_marker.clone(),
27416            replace_range_marker.clone(),
27417            insert_range_marker.clone(),
27418        ],
27419    );
27420
27421    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27422        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27423    ));
27424    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27425    let replace_range =
27426        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27427
27428    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27429        Some(ranges) if !ranges.is_empty() => {
27430            let range1 = ranges[0].clone();
27431            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27432        }
27433        _ => lsp::Range {
27434            start: replace_range.start,
27435            end: complete_from_position,
27436        },
27437    };
27438
27439    let mut request =
27440        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27441            let completions = completions.clone();
27442            counter.fetch_add(1, atomic::Ordering::Release);
27443            async move {
27444                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27445                assert_eq!(
27446                    params.text_document_position.position, complete_from_position,
27447                    "marker `|` position doesn't match",
27448                );
27449                Ok(Some(lsp::CompletionResponse::Array(
27450                    completions
27451                        .iter()
27452                        .map(|(label, new_text)| lsp::CompletionItem {
27453                            label: label.to_string(),
27454                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27455                                lsp::InsertReplaceEdit {
27456                                    insert: insert_range,
27457                                    replace: replace_range,
27458                                    new_text: new_text.to_string(),
27459                                },
27460                            )),
27461                            ..Default::default()
27462                        })
27463                        .collect(),
27464                )))
27465            }
27466        });
27467
27468    async move {
27469        request.next().await;
27470    }
27471}
27472
27473fn handle_resolve_completion_request(
27474    cx: &mut EditorLspTestContext,
27475    edits: Option<Vec<(&'static str, &'static str)>>,
27476) -> impl Future<Output = ()> {
27477    let edits = edits.map(|edits| {
27478        edits
27479            .iter()
27480            .map(|(marked_string, new_text)| {
27481                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27482                let replace_range = cx.to_lsp_range(
27483                    MultiBufferOffset(marked_ranges[0].start)
27484                        ..MultiBufferOffset(marked_ranges[0].end),
27485                );
27486                lsp::TextEdit::new(replace_range, new_text.to_string())
27487            })
27488            .collect::<Vec<_>>()
27489    });
27490
27491    let mut request =
27492        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27493            let edits = edits.clone();
27494            async move {
27495                Ok(lsp::CompletionItem {
27496                    additional_text_edits: edits,
27497                    ..Default::default()
27498                })
27499            }
27500        });
27501
27502    async move {
27503        request.next().await;
27504    }
27505}
27506
27507pub(crate) fn update_test_language_settings(
27508    cx: &mut TestAppContext,
27509    f: impl Fn(&mut AllLanguageSettingsContent),
27510) {
27511    cx.update(|cx| {
27512        SettingsStore::update_global(cx, |store, cx| {
27513            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27514        });
27515    });
27516}
27517
27518pub(crate) fn update_test_project_settings(
27519    cx: &mut TestAppContext,
27520    f: impl Fn(&mut ProjectSettingsContent),
27521) {
27522    cx.update(|cx| {
27523        SettingsStore::update_global(cx, |store, cx| {
27524            store.update_user_settings(cx, |settings| f(&mut settings.project));
27525        });
27526    });
27527}
27528
27529pub(crate) fn update_test_editor_settings(
27530    cx: &mut TestAppContext,
27531    f: impl Fn(&mut EditorSettingsContent),
27532) {
27533    cx.update(|cx| {
27534        SettingsStore::update_global(cx, |store, cx| {
27535            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27536        })
27537    })
27538}
27539
27540pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27541    cx.update(|cx| {
27542        assets::Assets.load_test_fonts(cx);
27543        let store = SettingsStore::test(cx);
27544        cx.set_global(store);
27545        theme::init(theme::LoadThemes::JustBase, cx);
27546        release_channel::init(semver::Version::new(0, 0, 0), cx);
27547        crate::init(cx);
27548    });
27549    zlog::init_test();
27550    update_test_language_settings(cx, f);
27551}
27552
27553#[track_caller]
27554fn assert_hunk_revert(
27555    not_reverted_text_with_selections: &str,
27556    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27557    expected_reverted_text_with_selections: &str,
27558    base_text: &str,
27559    cx: &mut EditorLspTestContext,
27560) {
27561    cx.set_state(not_reverted_text_with_selections);
27562    cx.set_head_text(base_text);
27563    cx.executor().run_until_parked();
27564
27565    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27566        let snapshot = editor.snapshot(window, cx);
27567        let reverted_hunk_statuses = snapshot
27568            .buffer_snapshot()
27569            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27570            .map(|hunk| hunk.status().kind)
27571            .collect::<Vec<_>>();
27572
27573        editor.git_restore(&Default::default(), window, cx);
27574        reverted_hunk_statuses
27575    });
27576    cx.executor().run_until_parked();
27577    cx.assert_editor_state(expected_reverted_text_with_selections);
27578    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27579}
27580
27581#[gpui::test(iterations = 10)]
27582async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27583    init_test(cx, |_| {});
27584
27585    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27586    let counter = diagnostic_requests.clone();
27587
27588    let fs = FakeFs::new(cx.executor());
27589    fs.insert_tree(
27590        path!("/a"),
27591        json!({
27592            "first.rs": "fn main() { let a = 5; }",
27593            "second.rs": "// Test file",
27594        }),
27595    )
27596    .await;
27597
27598    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27599    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27600    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27601
27602    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27603    language_registry.add(rust_lang());
27604    let mut fake_servers = language_registry.register_fake_lsp(
27605        "Rust",
27606        FakeLspAdapter {
27607            capabilities: lsp::ServerCapabilities {
27608                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27609                    lsp::DiagnosticOptions {
27610                        identifier: None,
27611                        inter_file_dependencies: true,
27612                        workspace_diagnostics: true,
27613                        work_done_progress_options: Default::default(),
27614                    },
27615                )),
27616                ..Default::default()
27617            },
27618            ..Default::default()
27619        },
27620    );
27621
27622    let editor = workspace
27623        .update(cx, |workspace, window, cx| {
27624            workspace.open_abs_path(
27625                PathBuf::from(path!("/a/first.rs")),
27626                OpenOptions::default(),
27627                window,
27628                cx,
27629            )
27630        })
27631        .unwrap()
27632        .await
27633        .unwrap()
27634        .downcast::<Editor>()
27635        .unwrap();
27636    let fake_server = fake_servers.next().await.unwrap();
27637    let server_id = fake_server.server.server_id();
27638    let mut first_request = fake_server
27639        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27640            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27641            let result_id = Some(new_result_id.to_string());
27642            assert_eq!(
27643                params.text_document.uri,
27644                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27645            );
27646            async move {
27647                Ok(lsp::DocumentDiagnosticReportResult::Report(
27648                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27649                        related_documents: None,
27650                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27651                            items: Vec::new(),
27652                            result_id,
27653                        },
27654                    }),
27655                ))
27656            }
27657        });
27658
27659    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
27660        project.update(cx, |project, cx| {
27661            let buffer_id = editor
27662                .read(cx)
27663                .buffer()
27664                .read(cx)
27665                .as_singleton()
27666                .expect("created a singleton buffer")
27667                .read(cx)
27668                .remote_id();
27669            let buffer_result_id = project
27670                .lsp_store()
27671                .read(cx)
27672                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27673            assert_eq!(expected_result_id, buffer_result_id);
27674        });
27675    };
27676
27677    ensure_result_id(None, cx);
27678    cx.executor().advance_clock(Duration::from_millis(60));
27679    cx.executor().run_until_parked();
27680    assert_eq!(
27681        diagnostic_requests.load(atomic::Ordering::Acquire),
27682        1,
27683        "Opening file should trigger diagnostic request"
27684    );
27685    first_request
27686        .next()
27687        .await
27688        .expect("should have sent the first diagnostics pull request");
27689    ensure_result_id(Some(SharedString::new_static("1")), cx);
27690
27691    // Editing should trigger diagnostics
27692    editor.update_in(cx, |editor, window, cx| {
27693        editor.handle_input("2", window, cx)
27694    });
27695    cx.executor().advance_clock(Duration::from_millis(60));
27696    cx.executor().run_until_parked();
27697    assert_eq!(
27698        diagnostic_requests.load(atomic::Ordering::Acquire),
27699        2,
27700        "Editing should trigger diagnostic request"
27701    );
27702    ensure_result_id(Some(SharedString::new_static("2")), cx);
27703
27704    // Moving cursor should not trigger diagnostic request
27705    editor.update_in(cx, |editor, window, cx| {
27706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27707            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27708        });
27709    });
27710    cx.executor().advance_clock(Duration::from_millis(60));
27711    cx.executor().run_until_parked();
27712    assert_eq!(
27713        diagnostic_requests.load(atomic::Ordering::Acquire),
27714        2,
27715        "Cursor movement should not trigger diagnostic request"
27716    );
27717    ensure_result_id(Some(SharedString::new_static("2")), cx);
27718    // Multiple rapid edits should be debounced
27719    for _ in 0..5 {
27720        editor.update_in(cx, |editor, window, cx| {
27721            editor.handle_input("x", window, cx)
27722        });
27723    }
27724    cx.executor().advance_clock(Duration::from_millis(60));
27725    cx.executor().run_until_parked();
27726
27727    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27728    assert!(
27729        final_requests <= 4,
27730        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27731    );
27732    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27733}
27734
27735#[gpui::test]
27736async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27737    // Regression test for issue #11671
27738    // Previously, adding a cursor after moving multiple cursors would reset
27739    // the cursor count instead of adding to the existing cursors.
27740    init_test(cx, |_| {});
27741    let mut cx = EditorTestContext::new(cx).await;
27742
27743    // Create a simple buffer with cursor at start
27744    cx.set_state(indoc! {"
27745        ˇaaaa
27746        bbbb
27747        cccc
27748        dddd
27749        eeee
27750        ffff
27751        gggg
27752        hhhh"});
27753
27754    // Add 2 cursors below (so we have 3 total)
27755    cx.update_editor(|editor, window, cx| {
27756        editor.add_selection_below(&Default::default(), window, cx);
27757        editor.add_selection_below(&Default::default(), window, cx);
27758    });
27759
27760    // Verify we have 3 cursors
27761    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27762    assert_eq!(
27763        initial_count, 3,
27764        "Should have 3 cursors after adding 2 below"
27765    );
27766
27767    // Move down one line
27768    cx.update_editor(|editor, window, cx| {
27769        editor.move_down(&MoveDown, window, cx);
27770    });
27771
27772    // Add another cursor below
27773    cx.update_editor(|editor, window, cx| {
27774        editor.add_selection_below(&Default::default(), window, cx);
27775    });
27776
27777    // Should now have 4 cursors (3 original + 1 new)
27778    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27779    assert_eq!(
27780        final_count, 4,
27781        "Should have 4 cursors after moving and adding another"
27782    );
27783}
27784
27785#[gpui::test]
27786async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27787    init_test(cx, |_| {});
27788
27789    let mut cx = EditorTestContext::new(cx).await;
27790
27791    cx.set_state(indoc!(
27792        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27793           Second line here"#
27794    ));
27795
27796    cx.update_editor(|editor, window, cx| {
27797        // Enable soft wrapping with a narrow width to force soft wrapping and
27798        // confirm that more than 2 rows are being displayed.
27799        editor.set_wrap_width(Some(100.0.into()), cx);
27800        assert!(editor.display_text(cx).lines().count() > 2);
27801
27802        editor.add_selection_below(
27803            &AddSelectionBelow {
27804                skip_soft_wrap: true,
27805            },
27806            window,
27807            cx,
27808        );
27809
27810        assert_eq!(
27811            display_ranges(editor, cx),
27812            &[
27813                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27814                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27815            ]
27816        );
27817
27818        editor.add_selection_above(
27819            &AddSelectionAbove {
27820                skip_soft_wrap: true,
27821            },
27822            window,
27823            cx,
27824        );
27825
27826        assert_eq!(
27827            display_ranges(editor, cx),
27828            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27829        );
27830
27831        editor.add_selection_below(
27832            &AddSelectionBelow {
27833                skip_soft_wrap: false,
27834            },
27835            window,
27836            cx,
27837        );
27838
27839        assert_eq!(
27840            display_ranges(editor, cx),
27841            &[
27842                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27843                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27844            ]
27845        );
27846
27847        editor.add_selection_above(
27848            &AddSelectionAbove {
27849                skip_soft_wrap: false,
27850            },
27851            window,
27852            cx,
27853        );
27854
27855        assert_eq!(
27856            display_ranges(editor, cx),
27857            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27858        );
27859    });
27860
27861    // Set up text where selections are in the middle of a soft-wrapped line.
27862    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27863    // selection should be at the same buffer column, not the same pixel
27864    // position.
27865    cx.set_state(indoc!(
27866        r#"1. Very long line to show «howˇ» a wrapped line would look
27867           2. Very long line to show how a wrapped line would look"#
27868    ));
27869
27870    cx.update_editor(|editor, window, cx| {
27871        // Enable soft wrapping with a narrow width to force soft wrapping and
27872        // confirm that more than 2 rows are being displayed.
27873        editor.set_wrap_width(Some(100.0.into()), cx);
27874        assert!(editor.display_text(cx).lines().count() > 2);
27875
27876        editor.add_selection_below(
27877            &AddSelectionBelow {
27878                skip_soft_wrap: true,
27879            },
27880            window,
27881            cx,
27882        );
27883
27884        // Assert that there's now 2 selections, both selecting the same column
27885        // range in the buffer row.
27886        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27887        let selections = editor.selections.all::<Point>(&display_map);
27888        assert_eq!(selections.len(), 2);
27889        assert_eq!(selections[0].start.column, selections[1].start.column);
27890        assert_eq!(selections[0].end.column, selections[1].end.column);
27891    });
27892}
27893
27894#[gpui::test]
27895async fn test_insert_snippet(cx: &mut TestAppContext) {
27896    init_test(cx, |_| {});
27897    let mut cx = EditorTestContext::new(cx).await;
27898
27899    cx.update_editor(|editor, _, cx| {
27900        editor.project().unwrap().update(cx, |project, cx| {
27901            project.snippets().update(cx, |snippets, _cx| {
27902                let snippet = project::snippet_provider::Snippet {
27903                    prefix: vec![], // no prefix needed!
27904                    body: "an Unspecified".to_string(),
27905                    description: Some("shhhh it's a secret".to_string()),
27906                    name: "super secret snippet".to_string(),
27907                };
27908                snippets.add_snippet_for_test(
27909                    None,
27910                    PathBuf::from("test_snippets.json"),
27911                    vec![Arc::new(snippet)],
27912                );
27913
27914                let snippet = project::snippet_provider::Snippet {
27915                    prefix: vec![], // no prefix needed!
27916                    body: " Location".to_string(),
27917                    description: Some("the word 'location'".to_string()),
27918                    name: "location word".to_string(),
27919                };
27920                snippets.add_snippet_for_test(
27921                    Some("Markdown".to_string()),
27922                    PathBuf::from("test_snippets.json"),
27923                    vec![Arc::new(snippet)],
27924                );
27925            });
27926        })
27927    });
27928
27929    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27930
27931    cx.update_editor(|editor, window, cx| {
27932        editor.insert_snippet_at_selections(
27933            &InsertSnippet {
27934                language: None,
27935                name: Some("super secret snippet".to_string()),
27936                snippet: None,
27937            },
27938            window,
27939            cx,
27940        );
27941
27942        // Language is specified in the action,
27943        // so the buffer language does not need to match
27944        editor.insert_snippet_at_selections(
27945            &InsertSnippet {
27946                language: Some("Markdown".to_string()),
27947                name: Some("location word".to_string()),
27948                snippet: None,
27949            },
27950            window,
27951            cx,
27952        );
27953
27954        editor.insert_snippet_at_selections(
27955            &InsertSnippet {
27956                language: None,
27957                name: None,
27958                snippet: Some("$0 after".to_string()),
27959            },
27960            window,
27961            cx,
27962        );
27963    });
27964
27965    cx.assert_editor_state(
27966        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27967    );
27968}
27969
27970#[gpui::test]
27971async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
27972    use crate::inlays::inlay_hints::InlayHintRefreshReason;
27973    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
27974    use settings::InlayHintSettingsContent;
27975    use std::sync::atomic::AtomicU32;
27976    use std::time::Duration;
27977
27978    const BASE_TIMEOUT_SECS: u64 = 1;
27979
27980    let request_count = Arc::new(AtomicU32::new(0));
27981    let closure_request_count = request_count.clone();
27982
27983    init_test(cx, |settings| {
27984        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
27985            enabled: Some(true),
27986            ..InlayHintSettingsContent::default()
27987        })
27988    });
27989    cx.update(|cx| {
27990        SettingsStore::update_global(cx, |store, cx| {
27991            store.update_user_settings(cx, |settings| {
27992                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
27993                    request_timeout: Some(BASE_TIMEOUT_SECS),
27994                    button: Some(true),
27995                    notifications: None,
27996                    semantic_token_rules: None,
27997                });
27998            });
27999        });
28000    });
28001
28002    let fs = FakeFs::new(cx.executor());
28003    fs.insert_tree(
28004        path!("/a"),
28005        json!({
28006            "main.rs": "fn main() { let a = 5; }",
28007        }),
28008    )
28009    .await;
28010
28011    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28012    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28013    language_registry.add(rust_lang());
28014    let mut fake_servers = language_registry.register_fake_lsp(
28015        "Rust",
28016        FakeLspAdapter {
28017            capabilities: lsp::ServerCapabilities {
28018                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
28019                ..lsp::ServerCapabilities::default()
28020            },
28021            initializer: Some(Box::new(move |fake_server| {
28022                let request_count = closure_request_count.clone();
28023                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
28024                    move |params, cx| {
28025                        let request_count = request_count.clone();
28026                        async move {
28027                            cx.background_executor()
28028                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
28029                                .await;
28030                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
28031                            assert_eq!(
28032                                params.text_document.uri,
28033                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
28034                            );
28035                            Ok(Some(vec![lsp::InlayHint {
28036                                position: lsp::Position::new(0, 1),
28037                                label: lsp::InlayHintLabel::String(count.to_string()),
28038                                kind: None,
28039                                text_edits: None,
28040                                tooltip: None,
28041                                padding_left: None,
28042                                padding_right: None,
28043                                data: None,
28044                            }]))
28045                        }
28046                    },
28047                );
28048            })),
28049            ..FakeLspAdapter::default()
28050        },
28051    );
28052
28053    let buffer = project
28054        .update(cx, |project, cx| {
28055            project.open_local_buffer(path!("/a/main.rs"), cx)
28056        })
28057        .await
28058        .unwrap();
28059    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
28060
28061    cx.executor().run_until_parked();
28062    let fake_server = fake_servers.next().await.unwrap();
28063
28064    cx.executor()
28065        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
28066    cx.executor().run_until_parked();
28067    editor
28068        .update(cx, |editor, _window, cx| {
28069            assert!(
28070                cached_hint_labels(editor, cx).is_empty(),
28071                "First request should time out, no hints cached"
28072            );
28073        })
28074        .unwrap();
28075
28076    editor
28077        .update(cx, |editor, _window, cx| {
28078            editor.refresh_inlay_hints(
28079                InlayHintRefreshReason::RefreshRequested {
28080                    server_id: fake_server.server.server_id(),
28081                    request_id: Some(1),
28082                },
28083                cx,
28084            );
28085        })
28086        .unwrap();
28087    cx.executor()
28088        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
28089    cx.executor().run_until_parked();
28090    editor
28091        .update(cx, |editor, _window, cx| {
28092            assert!(
28093                cached_hint_labels(editor, cx).is_empty(),
28094                "Second request should also time out with BASE_TIMEOUT, no hints cached"
28095            );
28096        })
28097        .unwrap();
28098
28099    cx.update(|cx| {
28100        SettingsStore::update_global(cx, |store, cx| {
28101            store.update_user_settings(cx, |settings| {
28102                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
28103                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
28104                    button: Some(true),
28105                    notifications: None,
28106                    semantic_token_rules: None,
28107                });
28108            });
28109        });
28110    });
28111    editor
28112        .update(cx, |editor, _window, cx| {
28113            editor.refresh_inlay_hints(
28114                InlayHintRefreshReason::RefreshRequested {
28115                    server_id: fake_server.server.server_id(),
28116                    request_id: Some(2),
28117                },
28118                cx,
28119            );
28120        })
28121        .unwrap();
28122    cx.executor()
28123        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
28124    cx.executor().run_until_parked();
28125    editor
28126        .update(cx, |editor, _window, cx| {
28127            assert_eq!(
28128                vec!["1".to_string()],
28129                cached_hint_labels(editor, cx),
28130                "With extended timeout (BASE * 4), hints should arrive successfully"
28131            );
28132            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
28133        })
28134        .unwrap();
28135}
28136
28137#[gpui::test]
28138async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
28139    init_test(cx, |_| {});
28140    let (editor, cx) = cx.add_window_view(Editor::single_line);
28141    editor.update_in(cx, |editor, window, cx| {
28142        editor.set_text("oops\n\nwow\n", window, cx)
28143    });
28144    cx.run_until_parked();
28145    editor.update(cx, |editor, cx| {
28146        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
28147    });
28148    editor.update(cx, |editor, cx| {
28149        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
28150    });
28151    cx.run_until_parked();
28152    editor.update(cx, |editor, cx| {
28153        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
28154    });
28155}
28156
28157#[gpui::test]
28158async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
28159    init_test(cx, |_| {});
28160
28161    cx.update(|cx| {
28162        register_project_item::<Editor>(cx);
28163    });
28164
28165    let fs = FakeFs::new(cx.executor());
28166    fs.insert_tree("/root1", json!({})).await;
28167    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
28168        .await;
28169
28170    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
28171    let (workspace, cx) =
28172        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
28173
28174    let worktree_id = project.update(cx, |project, cx| {
28175        project.worktrees(cx).next().unwrap().read(cx).id()
28176    });
28177
28178    let handle = workspace
28179        .update_in(cx, |workspace, window, cx| {
28180            let project_path = (worktree_id, rel_path("one.pdf"));
28181            workspace.open_path(project_path, None, true, window, cx)
28182        })
28183        .await
28184        .unwrap();
28185    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
28186    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
28187    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
28188    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
28189}
28190
28191#[gpui::test]
28192async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
28193    init_test(cx, |_| {});
28194
28195    let language = Arc::new(Language::new(
28196        LanguageConfig::default(),
28197        Some(tree_sitter_rust::LANGUAGE.into()),
28198    ));
28199
28200    // Test hierarchical sibling navigation
28201    let text = r#"
28202        fn outer() {
28203            if condition {
28204                let a = 1;
28205            }
28206            let b = 2;
28207        }
28208
28209        fn another() {
28210            let c = 3;
28211        }
28212    "#;
28213
28214    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
28215    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28216    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
28217
28218    // Wait for parsing to complete
28219    editor
28220        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
28221        .await;
28222
28223    editor.update_in(cx, |editor, window, cx| {
28224        // Start by selecting "let a = 1;" inside the if block
28225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28226            s.select_display_ranges([
28227                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
28228            ]);
28229        });
28230
28231        let initial_selection = editor
28232            .selections
28233            .display_ranges(&editor.display_snapshot(cx));
28234        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28235
28236        // Test select next sibling - should move up levels to find the next sibling
28237        // Since "let a = 1;" has no siblings in the if block, it should move up
28238        // to find "let b = 2;" which is a sibling of the if block
28239        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28240        let next_selection = editor
28241            .selections
28242            .display_ranges(&editor.display_snapshot(cx));
28243
28244        // Should have a selection and it should be different from the initial
28245        assert_eq!(
28246            next_selection.len(),
28247            1,
28248            "Should have one selection after next"
28249        );
28250        assert_ne!(
28251            next_selection[0], initial_selection[0],
28252            "Next sibling selection should be different"
28253        );
28254
28255        // Test hierarchical navigation by going to the end of the current function
28256        // and trying to navigate to the next function
28257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28258            s.select_display_ranges([
28259                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28260            ]);
28261        });
28262
28263        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28264        let function_next_selection = editor
28265            .selections
28266            .display_ranges(&editor.display_snapshot(cx));
28267
28268        // Should move to the next function
28269        assert_eq!(
28270            function_next_selection.len(),
28271            1,
28272            "Should have one selection after function next"
28273        );
28274
28275        // Test select previous sibling navigation
28276        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28277        let prev_selection = editor
28278            .selections
28279            .display_ranges(&editor.display_snapshot(cx));
28280
28281        // Should have a selection and it should be different
28282        assert_eq!(
28283            prev_selection.len(),
28284            1,
28285            "Should have one selection after prev"
28286        );
28287        assert_ne!(
28288            prev_selection[0], function_next_selection[0],
28289            "Previous sibling selection should be different from next"
28290        );
28291    });
28292}
28293
28294#[gpui::test]
28295async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28296    init_test(cx, |_| {});
28297
28298    let mut cx = EditorTestContext::new(cx).await;
28299    cx.set_state(
28300        "let ˇvariable = 42;
28301let another = variable + 1;
28302let result = variable * 2;",
28303    );
28304
28305    // Set up document highlights manually (simulating LSP response)
28306    cx.update_editor(|editor, _window, cx| {
28307        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28308
28309        // Create highlights for "variable" occurrences
28310        let highlight_ranges = [
28311            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28312            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28313            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28314        ];
28315
28316        let anchor_ranges: Vec<_> = highlight_ranges
28317            .iter()
28318            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28319            .collect();
28320
28321        editor.highlight_background(
28322            HighlightKey::DocumentHighlightRead,
28323            &anchor_ranges,
28324            |_, theme| theme.colors().editor_document_highlight_read_background,
28325            cx,
28326        );
28327    });
28328
28329    // Go to next highlight - should move to second "variable"
28330    cx.update_editor(|editor, window, cx| {
28331        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28332    });
28333    cx.assert_editor_state(
28334        "let variable = 42;
28335let another = ˇvariable + 1;
28336let result = variable * 2;",
28337    );
28338
28339    // Go to next highlight - should move to third "variable"
28340    cx.update_editor(|editor, window, cx| {
28341        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28342    });
28343    cx.assert_editor_state(
28344        "let variable = 42;
28345let another = variable + 1;
28346let result = ˇvariable * 2;",
28347    );
28348
28349    // Go to next highlight - should stay at third "variable" (no wrap-around)
28350    cx.update_editor(|editor, window, cx| {
28351        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28352    });
28353    cx.assert_editor_state(
28354        "let variable = 42;
28355let another = variable + 1;
28356let result = ˇvariable * 2;",
28357    );
28358
28359    // Now test going backwards from third position
28360    cx.update_editor(|editor, window, cx| {
28361        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28362    });
28363    cx.assert_editor_state(
28364        "let variable = 42;
28365let another = ˇvariable + 1;
28366let result = variable * 2;",
28367    );
28368
28369    // Go to previous highlight - should move to first "variable"
28370    cx.update_editor(|editor, window, cx| {
28371        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28372    });
28373    cx.assert_editor_state(
28374        "let ˇvariable = 42;
28375let another = variable + 1;
28376let result = variable * 2;",
28377    );
28378
28379    // Go to previous highlight - should stay on first "variable"
28380    cx.update_editor(|editor, window, cx| {
28381        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28382    });
28383    cx.assert_editor_state(
28384        "let ˇvariable = 42;
28385let another = variable + 1;
28386let result = variable * 2;",
28387    );
28388}
28389
28390#[gpui::test]
28391async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28392    cx: &mut gpui::TestAppContext,
28393) {
28394    init_test(cx, |_| {});
28395
28396    let url = "https://zed.dev";
28397
28398    let markdown_language = Arc::new(Language::new(
28399        LanguageConfig {
28400            name: "Markdown".into(),
28401            ..LanguageConfig::default()
28402        },
28403        None,
28404    ));
28405
28406    let mut cx = EditorTestContext::new(cx).await;
28407    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28408    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28409
28410    cx.update_editor(|editor, window, cx| {
28411        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28412        editor.paste(&Paste, window, cx);
28413    });
28414
28415    cx.assert_editor_state(&format!(
28416        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28417    ));
28418}
28419
28420#[gpui::test]
28421async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28422    init_test(cx, |_| {});
28423
28424    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28425    let mut cx = EditorTestContext::new(cx).await;
28426
28427    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28428
28429    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28430    cx.set_state(&indoc! {"
28431        - [ ] Item 1
28432            - [ ] Item 1.a
28433        - [ˇ] Item 2
28434            - [ˇ] Item 2.a
28435            - [ˇ] Item 2.b
28436        "
28437    });
28438    cx.update_editor(|editor, window, cx| {
28439        editor.handle_input("x", window, cx);
28440    });
28441    cx.run_until_parked();
28442    cx.assert_editor_state(indoc! {"
28443        - [ ] Item 1
28444            - [ ] Item 1.a
28445        - [xˇ] Item 2
28446            - [xˇ] Item 2.a
28447            - [xˇ] Item 2.b
28448        "
28449    });
28450
28451    // Case 2: Test adding new line after nested list continues the list with unchecked task
28452    cx.set_state(&indoc! {"
28453        - [ ] Item 1
28454            - [ ] Item 1.a
28455        - [x] Item 2
28456            - [x] Item 2.a
28457            - [x] Item 2.bˇ"
28458    });
28459    cx.update_editor(|editor, window, cx| {
28460        editor.newline(&Newline, window, cx);
28461    });
28462    cx.assert_editor_state(indoc! {"
28463        - [ ] Item 1
28464            - [ ] Item 1.a
28465        - [x] Item 2
28466            - [x] Item 2.a
28467            - [x] Item 2.b
28468            - [ ] ˇ"
28469    });
28470
28471    // Case 3: Test adding content to continued list item
28472    cx.update_editor(|editor, window, cx| {
28473        editor.handle_input("Item 2.c", window, cx);
28474    });
28475    cx.run_until_parked();
28476    cx.assert_editor_state(indoc! {"
28477        - [ ] Item 1
28478            - [ ] Item 1.a
28479        - [x] Item 2
28480            - [x] Item 2.a
28481            - [x] Item 2.b
28482            - [ ] Item 2.cˇ"
28483    });
28484
28485    // Case 4: Test adding new line after nested ordered list continues with next number
28486    cx.set_state(indoc! {"
28487        1. Item 1
28488            1. Item 1.a
28489        2. Item 2
28490            1. Item 2.a
28491            2. Item 2.bˇ"
28492    });
28493    cx.update_editor(|editor, window, cx| {
28494        editor.newline(&Newline, window, cx);
28495    });
28496    cx.assert_editor_state(indoc! {"
28497        1. Item 1
28498            1. Item 1.a
28499        2. Item 2
28500            1. Item 2.a
28501            2. Item 2.b
28502            3. ˇ"
28503    });
28504
28505    // Case 5: Adding content to continued ordered list item
28506    cx.update_editor(|editor, window, cx| {
28507        editor.handle_input("Item 2.c", window, cx);
28508    });
28509    cx.run_until_parked();
28510    cx.assert_editor_state(indoc! {"
28511        1. Item 1
28512            1. Item 1.a
28513        2. Item 2
28514            1. Item 2.a
28515            2. Item 2.b
28516            3. Item 2.cˇ"
28517    });
28518
28519    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28520    cx.set_state(indoc! {"
28521        - Item 1
28522            - Item 1.a
28523            - Item 1.a
28524        ˇ"});
28525    cx.update_editor(|editor, window, cx| {
28526        editor.handle_input("-", window, cx);
28527    });
28528    cx.run_until_parked();
28529    cx.assert_editor_state(indoc! {"
28530        - Item 1
28531            - Item 1.a
28532            - Item 1.a
28533"});
28534
28535    // Case 7: Test blockquote newline preserves something
28536    cx.set_state(indoc! {"
28537        > Item 1ˇ"
28538    });
28539    cx.update_editor(|editor, window, cx| {
28540        editor.newline(&Newline, window, cx);
28541    });
28542    cx.assert_editor_state(indoc! {"
28543        > Item 1
28544        ˇ"
28545    });
28546}
28547
28548#[gpui::test]
28549async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28550    cx: &mut gpui::TestAppContext,
28551) {
28552    init_test(cx, |_| {});
28553
28554    let url = "https://zed.dev";
28555
28556    let markdown_language = Arc::new(Language::new(
28557        LanguageConfig {
28558            name: "Markdown".into(),
28559            ..LanguageConfig::default()
28560        },
28561        None,
28562    ));
28563
28564    let mut cx = EditorTestContext::new(cx).await;
28565    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28566    cx.set_state(&format!(
28567        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28568    ));
28569
28570    cx.update_editor(|editor, window, cx| {
28571        editor.copy(&Copy, window, cx);
28572    });
28573
28574    cx.set_state(&format!(
28575        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28576    ));
28577
28578    cx.update_editor(|editor, window, cx| {
28579        editor.paste(&Paste, window, cx);
28580    });
28581
28582    cx.assert_editor_state(&format!(
28583        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28584    ));
28585}
28586
28587#[gpui::test]
28588async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28589    cx: &mut gpui::TestAppContext,
28590) {
28591    init_test(cx, |_| {});
28592
28593    let url = "https://zed.dev";
28594
28595    let markdown_language = Arc::new(Language::new(
28596        LanguageConfig {
28597            name: "Markdown".into(),
28598            ..LanguageConfig::default()
28599        },
28600        None,
28601    ));
28602
28603    let mut cx = EditorTestContext::new(cx).await;
28604    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28605    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28606
28607    cx.update_editor(|editor, window, cx| {
28608        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28609        editor.paste(&Paste, window, cx);
28610    });
28611
28612    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28613}
28614
28615#[gpui::test]
28616async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28617    cx: &mut gpui::TestAppContext,
28618) {
28619    init_test(cx, |_| {});
28620
28621    let text = "Awesome";
28622
28623    let markdown_language = Arc::new(Language::new(
28624        LanguageConfig {
28625            name: "Markdown".into(),
28626            ..LanguageConfig::default()
28627        },
28628        None,
28629    ));
28630
28631    let mut cx = EditorTestContext::new(cx).await;
28632    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28633    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28634
28635    cx.update_editor(|editor, window, cx| {
28636        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28637        editor.paste(&Paste, window, cx);
28638    });
28639
28640    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28641}
28642
28643#[gpui::test]
28644async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28645    cx: &mut gpui::TestAppContext,
28646) {
28647    init_test(cx, |_| {});
28648
28649    let url = "https://zed.dev";
28650
28651    let markdown_language = Arc::new(Language::new(
28652        LanguageConfig {
28653            name: "Rust".into(),
28654            ..LanguageConfig::default()
28655        },
28656        None,
28657    ));
28658
28659    let mut cx = EditorTestContext::new(cx).await;
28660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28661    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28662
28663    cx.update_editor(|editor, window, cx| {
28664        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28665        editor.paste(&Paste, window, cx);
28666    });
28667
28668    cx.assert_editor_state(&format!(
28669        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28670    ));
28671}
28672
28673#[gpui::test]
28674async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28675    cx: &mut TestAppContext,
28676) {
28677    init_test(cx, |_| {});
28678
28679    let url = "https://zed.dev";
28680
28681    let markdown_language = Arc::new(Language::new(
28682        LanguageConfig {
28683            name: "Markdown".into(),
28684            ..LanguageConfig::default()
28685        },
28686        None,
28687    ));
28688
28689    let (editor, cx) = cx.add_window_view(|window, cx| {
28690        let multi_buffer = MultiBuffer::build_multi(
28691            [
28692                ("this will embed -> link", vec![Point::row_range(0..1)]),
28693                ("this will replace -> link", vec![Point::row_range(0..1)]),
28694            ],
28695            cx,
28696        );
28697        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28699            s.select_ranges(vec![
28700                Point::new(0, 19)..Point::new(0, 23),
28701                Point::new(1, 21)..Point::new(1, 25),
28702            ])
28703        });
28704        let first_buffer_id = multi_buffer
28705            .read(cx)
28706            .excerpt_buffer_ids()
28707            .into_iter()
28708            .next()
28709            .unwrap();
28710        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28711        first_buffer.update(cx, |buffer, cx| {
28712            buffer.set_language(Some(markdown_language.clone()), cx);
28713        });
28714
28715        editor
28716    });
28717    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28718
28719    cx.update_editor(|editor, window, cx| {
28720        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28721        editor.paste(&Paste, window, cx);
28722    });
28723
28724    cx.assert_editor_state(&format!(
28725        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28726    ));
28727}
28728
28729#[gpui::test]
28730async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28731    init_test(cx, |_| {});
28732
28733    let fs = FakeFs::new(cx.executor());
28734    fs.insert_tree(
28735        path!("/project"),
28736        json!({
28737            "first.rs": "# First Document\nSome content here.",
28738            "second.rs": "Plain text content for second file.",
28739        }),
28740    )
28741    .await;
28742
28743    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28744    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28745    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28746
28747    let language = rust_lang();
28748    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28749    language_registry.add(language.clone());
28750    let mut fake_servers = language_registry.register_fake_lsp(
28751        "Rust",
28752        FakeLspAdapter {
28753            ..FakeLspAdapter::default()
28754        },
28755    );
28756
28757    let buffer1 = project
28758        .update(cx, |project, cx| {
28759            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28760        })
28761        .await
28762        .unwrap();
28763    let buffer2 = project
28764        .update(cx, |project, cx| {
28765            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28766        })
28767        .await
28768        .unwrap();
28769
28770    let multi_buffer = cx.new(|cx| {
28771        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28772        multi_buffer.set_excerpts_for_path(
28773            PathKey::for_buffer(&buffer1, cx),
28774            buffer1.clone(),
28775            [Point::zero()..buffer1.read(cx).max_point()],
28776            3,
28777            cx,
28778        );
28779        multi_buffer.set_excerpts_for_path(
28780            PathKey::for_buffer(&buffer2, cx),
28781            buffer2.clone(),
28782            [Point::zero()..buffer1.read(cx).max_point()],
28783            3,
28784            cx,
28785        );
28786        multi_buffer
28787    });
28788
28789    let (editor, cx) = cx.add_window_view(|window, cx| {
28790        Editor::new(
28791            EditorMode::full(),
28792            multi_buffer,
28793            Some(project.clone()),
28794            window,
28795            cx,
28796        )
28797    });
28798
28799    let fake_language_server = fake_servers.next().await.unwrap();
28800
28801    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28802
28803    let save = editor.update_in(cx, |editor, window, cx| {
28804        assert!(editor.is_dirty(cx));
28805
28806        editor.save(
28807            SaveOptions {
28808                format: true,
28809                autosave: true,
28810            },
28811            project,
28812            window,
28813            cx,
28814        )
28815    });
28816    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28817    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28818    let mut done_edit_rx = Some(done_edit_rx);
28819    let mut start_edit_tx = Some(start_edit_tx);
28820
28821    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28822        start_edit_tx.take().unwrap().send(()).unwrap();
28823        let done_edit_rx = done_edit_rx.take().unwrap();
28824        async move {
28825            done_edit_rx.await.unwrap();
28826            Ok(None)
28827        }
28828    });
28829
28830    start_edit_rx.await.unwrap();
28831    buffer2
28832        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28833        .unwrap();
28834
28835    done_edit_tx.send(()).unwrap();
28836
28837    save.await.unwrap();
28838    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28839}
28840
28841#[gpui::test]
28842fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28843    init_test(cx, |_| {});
28844
28845    let editor = cx.add_window(|window, cx| {
28846        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28847        build_editor(buffer, window, cx)
28848    });
28849
28850    editor
28851        .update(cx, |editor, window, cx| {
28852            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28853                s.select_display_ranges([
28854                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28855                ])
28856            });
28857
28858            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28859
28860            assert_eq!(
28861                editor.display_text(cx),
28862                "line1\nline2\nline2",
28863                "Duplicating last line upward should create duplicate above, not on same line"
28864            );
28865
28866            assert_eq!(
28867                editor
28868                    .selections
28869                    .display_ranges(&editor.display_snapshot(cx)),
28870                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28871                "Selection should move to the duplicated line"
28872            );
28873        })
28874        .unwrap();
28875}
28876
28877#[gpui::test]
28878async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28879    init_test(cx, |_| {});
28880
28881    let mut cx = EditorTestContext::new(cx).await;
28882
28883    cx.set_state("line1\nline2ˇ");
28884
28885    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28886
28887    let clipboard_text = cx
28888        .read_from_clipboard()
28889        .and_then(|item| item.text().as_deref().map(str::to_string));
28890
28891    assert_eq!(
28892        clipboard_text,
28893        Some("line2\n".to_string()),
28894        "Copying a line without trailing newline should include a newline"
28895    );
28896
28897    cx.set_state("line1\nˇ");
28898
28899    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28900
28901    cx.assert_editor_state("line1\nline2\nˇ");
28902}
28903
28904#[gpui::test]
28905async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28906    init_test(cx, |_| {});
28907
28908    let mut cx = EditorTestContext::new(cx).await;
28909
28910    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28911
28912    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28913
28914    let clipboard_text = cx
28915        .read_from_clipboard()
28916        .and_then(|item| item.text().as_deref().map(str::to_string));
28917
28918    assert_eq!(
28919        clipboard_text,
28920        Some("line1\nline2\nline3\n".to_string()),
28921        "Copying multiple lines should include a single newline between lines"
28922    );
28923
28924    cx.set_state("lineA\nˇ");
28925
28926    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28927
28928    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28929}
28930
28931#[gpui::test]
28932async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28933    init_test(cx, |_| {});
28934
28935    let mut cx = EditorTestContext::new(cx).await;
28936
28937    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28938
28939    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28940
28941    let clipboard_text = cx
28942        .read_from_clipboard()
28943        .and_then(|item| item.text().as_deref().map(str::to_string));
28944
28945    assert_eq!(
28946        clipboard_text,
28947        Some("line1\nline2\nline3\n".to_string()),
28948        "Copying multiple lines should include a single newline between lines"
28949    );
28950
28951    cx.set_state("lineA\nˇ");
28952
28953    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28954
28955    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28956}
28957
28958#[gpui::test]
28959async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28960    init_test(cx, |_| {});
28961
28962    let mut cx = EditorTestContext::new(cx).await;
28963
28964    cx.set_state("line1\nline2ˇ");
28965    cx.update_editor(|e, window, cx| {
28966        e.set_mode(EditorMode::SingleLine);
28967        assert!(e.key_context(window, cx).contains("end_of_input"));
28968    });
28969    cx.set_state("ˇline1\nline2");
28970    cx.update_editor(|e, window, cx| {
28971        assert!(!e.key_context(window, cx).contains("end_of_input"));
28972    });
28973    cx.set_state("line1ˇ\nline2");
28974    cx.update_editor(|e, window, cx| {
28975        assert!(!e.key_context(window, cx).contains("end_of_input"));
28976    });
28977}
28978
28979#[gpui::test]
28980async fn test_sticky_scroll(cx: &mut TestAppContext) {
28981    init_test(cx, |_| {});
28982    let mut cx = EditorTestContext::new(cx).await;
28983
28984    let buffer = indoc! {"
28985            ˇfn foo() {
28986                let abc = 123;
28987            }
28988            struct Bar;
28989            impl Bar {
28990                fn new() -> Self {
28991                    Self
28992                }
28993            }
28994            fn baz() {
28995            }
28996        "};
28997    cx.set_state(&buffer);
28998
28999    cx.update_editor(|e, _, cx| {
29000        e.buffer()
29001            .read(cx)
29002            .as_singleton()
29003            .unwrap()
29004            .update(cx, |buffer, cx| {
29005                buffer.set_language(Some(rust_lang()), cx);
29006            })
29007    });
29008
29009    let mut sticky_headers = |offset: ScrollOffset| {
29010        cx.update_editor(|e, window, cx| {
29011            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
29012        });
29013        cx.run_until_parked();
29014        cx.update_editor(|e, window, cx| {
29015            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
29016                .into_iter()
29017                .map(
29018                    |StickyHeader {
29019                         start_point,
29020                         offset,
29021                         ..
29022                     }| { (start_point, offset) },
29023                )
29024                .collect::<Vec<_>>()
29025        })
29026    };
29027
29028    let fn_foo = Point { row: 0, column: 0 };
29029    let impl_bar = Point { row: 4, column: 0 };
29030    let fn_new = Point { row: 5, column: 4 };
29031
29032    assert_eq!(sticky_headers(0.0), vec![]);
29033    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
29034    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
29035    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
29036    assert_eq!(sticky_headers(2.0), vec![]);
29037    assert_eq!(sticky_headers(2.5), vec![]);
29038    assert_eq!(sticky_headers(3.0), vec![]);
29039    assert_eq!(sticky_headers(3.5), vec![]);
29040    assert_eq!(sticky_headers(4.0), vec![]);
29041    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29042    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29043    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
29044    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
29045    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
29046    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
29047    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
29048    assert_eq!(sticky_headers(8.0), vec![]);
29049    assert_eq!(sticky_headers(8.5), vec![]);
29050    assert_eq!(sticky_headers(9.0), vec![]);
29051    assert_eq!(sticky_headers(9.5), vec![]);
29052    assert_eq!(sticky_headers(10.0), vec![]);
29053}
29054
29055#[gpui::test]
29056fn test_relative_line_numbers(cx: &mut TestAppContext) {
29057    init_test(cx, |_| {});
29058
29059    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
29060    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
29061    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
29062
29063    let multibuffer = cx.new(|cx| {
29064        let mut multibuffer = MultiBuffer::new(ReadWrite);
29065        multibuffer.push_excerpts(
29066            buffer_1.clone(),
29067            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29068            cx,
29069        );
29070        multibuffer.push_excerpts(
29071            buffer_2.clone(),
29072            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29073            cx,
29074        );
29075        multibuffer.push_excerpts(
29076            buffer_3.clone(),
29077            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29078            cx,
29079        );
29080        multibuffer
29081    });
29082
29083    // wrapped contents of multibuffer:
29084    //    aaa
29085    //    aaa
29086    //    aaa
29087    //    a
29088    //    bbb
29089    //
29090    //    ccc
29091    //    ccc
29092    //    ccc
29093    //    c
29094    //    ddd
29095    //
29096    //    eee
29097    //    fff
29098    //    fff
29099    //    fff
29100    //    f
29101
29102    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
29103    _ = editor.update(cx, |editor, window, cx| {
29104        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
29105
29106        // includes trailing newlines.
29107        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
29108        let expected_wrapped_line_numbers = [
29109            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
29110        ];
29111
29112        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29113            s.select_ranges([
29114                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
29115            ]);
29116        });
29117
29118        let snapshot = editor.snapshot(window, cx);
29119
29120        // these are all 0-indexed
29121        let base_display_row = DisplayRow(11);
29122        let base_row = 3;
29123        let wrapped_base_row = 7;
29124
29125        // test not counting wrapped lines
29126        let expected_relative_numbers = expected_line_numbers
29127            .into_iter()
29128            .enumerate()
29129            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
29130            .collect_vec();
29131        let actual_relative_numbers = snapshot
29132            .calculate_relative_line_numbers(
29133                &(DisplayRow(0)..DisplayRow(24)),
29134                base_display_row,
29135                false,
29136            )
29137            .into_iter()
29138            .sorted()
29139            .collect_vec();
29140        assert_eq!(expected_relative_numbers, actual_relative_numbers);
29141        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
29142        for (display_row, relative_number) in expected_relative_numbers {
29143            assert_eq!(
29144                relative_number,
29145                snapshot
29146                    .relative_line_delta(display_row, base_display_row, false)
29147                    .unsigned_abs() as u32,
29148            );
29149        }
29150
29151        // test counting wrapped lines
29152        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
29153            .into_iter()
29154            .enumerate()
29155            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
29156            .filter(|(row, _)| *row != base_display_row)
29157            .collect_vec();
29158        let actual_relative_numbers = snapshot
29159            .calculate_relative_line_numbers(
29160                &(DisplayRow(0)..DisplayRow(24)),
29161                base_display_row,
29162                true,
29163            )
29164            .into_iter()
29165            .sorted()
29166            .collect_vec();
29167        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
29168        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
29169        for (display_row, relative_number) in expected_wrapped_relative_numbers {
29170            assert_eq!(
29171                relative_number,
29172                snapshot
29173                    .relative_line_delta(display_row, base_display_row, true)
29174                    .unsigned_abs() as u32,
29175            );
29176        }
29177    });
29178}
29179
29180#[gpui::test]
29181async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
29182    init_test(cx, |_| {});
29183    cx.update(|cx| {
29184        SettingsStore::update_global(cx, |store, cx| {
29185            store.update_user_settings(cx, |settings| {
29186                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
29187                    enabled: Some(true),
29188                })
29189            });
29190        });
29191    });
29192    let mut cx = EditorTestContext::new(cx).await;
29193
29194    let line_height = cx.update_editor(|editor, window, cx| {
29195        editor
29196            .style(cx)
29197            .text
29198            .line_height_in_pixels(window.rem_size())
29199    });
29200
29201    let buffer = indoc! {"
29202            ˇfn foo() {
29203                let abc = 123;
29204            }
29205            struct Bar;
29206            impl Bar {
29207                fn new() -> Self {
29208                    Self
29209                }
29210            }
29211            fn baz() {
29212            }
29213        "};
29214    cx.set_state(&buffer);
29215
29216    cx.update_editor(|e, _, cx| {
29217        e.buffer()
29218            .read(cx)
29219            .as_singleton()
29220            .unwrap()
29221            .update(cx, |buffer, cx| {
29222                buffer.set_language(Some(rust_lang()), cx);
29223            })
29224    });
29225
29226    let fn_foo = || empty_range(0, 0);
29227    let impl_bar = || empty_range(4, 0);
29228    let fn_new = || empty_range(5, 4);
29229
29230    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29231        cx.update_editor(|e, window, cx| {
29232            e.scroll(
29233                gpui::Point {
29234                    x: 0.,
29235                    y: scroll_offset,
29236                },
29237                None,
29238                window,
29239                cx,
29240            );
29241        });
29242        cx.run_until_parked();
29243        cx.simulate_click(
29244            gpui::Point {
29245                x: px(0.),
29246                y: click_offset as f32 * line_height,
29247            },
29248            Modifiers::none(),
29249        );
29250        cx.run_until_parked();
29251        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29252    };
29253    assert_eq!(
29254        scroll_and_click(
29255            4.5, // impl Bar is halfway off the screen
29256            0.0  // click top of screen
29257        ),
29258        // scrolled to impl Bar
29259        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29260    );
29261
29262    assert_eq!(
29263        scroll_and_click(
29264            4.5,  // impl Bar is halfway off the screen
29265            0.25  // click middle of impl Bar
29266        ),
29267        // scrolled to impl Bar
29268        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29269    );
29270
29271    assert_eq!(
29272        scroll_and_click(
29273            4.5, // impl Bar is halfway off the screen
29274            1.5  // click below impl Bar (e.g. fn new())
29275        ),
29276        // scrolled to fn new() - this is below the impl Bar header which has persisted
29277        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29278    );
29279
29280    assert_eq!(
29281        scroll_and_click(
29282            5.5,  // fn new is halfway underneath impl Bar
29283            0.75  // click on the overlap of impl Bar and fn new()
29284        ),
29285        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29286    );
29287
29288    assert_eq!(
29289        scroll_and_click(
29290            5.5,  // fn new is halfway underneath impl Bar
29291            1.25  // click on the visible part of fn new()
29292        ),
29293        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29294    );
29295
29296    assert_eq!(
29297        scroll_and_click(
29298            1.5, // fn foo is halfway off the screen
29299            0.0  // click top of screen
29300        ),
29301        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29302    );
29303
29304    assert_eq!(
29305        scroll_and_click(
29306            1.5,  // fn foo is halfway off the screen
29307            0.75  // click visible part of let abc...
29308        )
29309        .0,
29310        // no change in scroll
29311        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29312        (gpui::Point { x: 0., y: 1.5 })
29313    );
29314}
29315
29316#[gpui::test]
29317async fn test_next_prev_reference(cx: &mut TestAppContext) {
29318    const CYCLE_POSITIONS: &[&'static str] = &[
29319        indoc! {"
29320            fn foo() {
29321                let ˇabc = 123;
29322                let x = abc + 1;
29323                let y = abc + 2;
29324                let z = abc + 2;
29325            }
29326        "},
29327        indoc! {"
29328            fn foo() {
29329                let abc = 123;
29330                let x = ˇabc + 1;
29331                let y = abc + 2;
29332                let z = abc + 2;
29333            }
29334        "},
29335        indoc! {"
29336            fn foo() {
29337                let abc = 123;
29338                let x = abc + 1;
29339                let y = ˇabc + 2;
29340                let z = abc + 2;
29341            }
29342        "},
29343        indoc! {"
29344            fn foo() {
29345                let abc = 123;
29346                let x = abc + 1;
29347                let y = abc + 2;
29348                let z = ˇabc + 2;
29349            }
29350        "},
29351    ];
29352
29353    init_test(cx, |_| {});
29354
29355    let mut cx = EditorLspTestContext::new_rust(
29356        lsp::ServerCapabilities {
29357            references_provider: Some(lsp::OneOf::Left(true)),
29358            ..Default::default()
29359        },
29360        cx,
29361    )
29362    .await;
29363
29364    // importantly, the cursor is in the middle
29365    cx.set_state(indoc! {"
29366        fn foo() {
29367            let aˇbc = 123;
29368            let x = abc + 1;
29369            let y = abc + 2;
29370            let z = abc + 2;
29371        }
29372    "});
29373
29374    let reference_ranges = [
29375        lsp::Position::new(1, 8),
29376        lsp::Position::new(2, 12),
29377        lsp::Position::new(3, 12),
29378        lsp::Position::new(4, 12),
29379    ]
29380    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29381
29382    cx.lsp
29383        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29384            Ok(Some(
29385                reference_ranges
29386                    .map(|range| lsp::Location {
29387                        uri: params.text_document_position.text_document.uri.clone(),
29388                        range,
29389                    })
29390                    .to_vec(),
29391            ))
29392        });
29393
29394    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29395        cx.update_editor(|editor, window, cx| {
29396            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29397        })
29398        .unwrap()
29399        .await
29400        .unwrap()
29401    };
29402
29403    _move(Direction::Next, 1, &mut cx).await;
29404    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29405
29406    _move(Direction::Next, 1, &mut cx).await;
29407    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29408
29409    _move(Direction::Next, 1, &mut cx).await;
29410    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29411
29412    // loops back to the start
29413    _move(Direction::Next, 1, &mut cx).await;
29414    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29415
29416    // loops back to the end
29417    _move(Direction::Prev, 1, &mut cx).await;
29418    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29419
29420    _move(Direction::Prev, 1, &mut cx).await;
29421    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29422
29423    _move(Direction::Prev, 1, &mut cx).await;
29424    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29425
29426    _move(Direction::Prev, 1, &mut cx).await;
29427    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29428
29429    _move(Direction::Next, 3, &mut cx).await;
29430    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29431
29432    _move(Direction::Prev, 2, &mut cx).await;
29433    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29434}
29435
29436#[gpui::test]
29437async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29438    init_test(cx, |_| {});
29439
29440    let (editor, cx) = cx.add_window_view(|window, cx| {
29441        let multi_buffer = MultiBuffer::build_multi(
29442            [
29443                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29444                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29445            ],
29446            cx,
29447        );
29448        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29449    });
29450
29451    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29452    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29453
29454    cx.assert_excerpts_with_selections(indoc! {"
29455        [EXCERPT]
29456        ˇ1
29457        2
29458        3
29459        [EXCERPT]
29460        1
29461        2
29462        3
29463        "});
29464
29465    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29466    cx.update_editor(|editor, window, cx| {
29467        editor.change_selections(None.into(), window, cx, |s| {
29468            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29469        });
29470    });
29471    cx.assert_excerpts_with_selections(indoc! {"
29472        [EXCERPT]
29473        1
2947429475        3
29476        [EXCERPT]
29477        1
29478        2
29479        3
29480        "});
29481
29482    cx.update_editor(|editor, window, cx| {
29483        editor
29484            .select_all_matches(&SelectAllMatches, window, cx)
29485            .unwrap();
29486    });
29487    cx.assert_excerpts_with_selections(indoc! {"
29488        [EXCERPT]
29489        1
2949029491        3
29492        [EXCERPT]
29493        1
2949429495        3
29496        "});
29497
29498    cx.update_editor(|editor, window, cx| {
29499        editor.handle_input("X", window, cx);
29500    });
29501    cx.assert_excerpts_with_selections(indoc! {"
29502        [EXCERPT]
29503        1
2950429505        3
29506        [EXCERPT]
29507        1
2950829509        3
29510        "});
29511
29512    // Scenario 2: Select "2", then fold second buffer before insertion
29513    cx.update_multibuffer(|mb, cx| {
29514        for buffer_id in buffer_ids.iter() {
29515            let buffer = mb.buffer(*buffer_id).unwrap();
29516            buffer.update(cx, |buffer, cx| {
29517                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29518            });
29519        }
29520    });
29521
29522    // Select "2" and select all matches
29523    cx.update_editor(|editor, window, cx| {
29524        editor.change_selections(None.into(), window, cx, |s| {
29525            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29526        });
29527        editor
29528            .select_all_matches(&SelectAllMatches, window, cx)
29529            .unwrap();
29530    });
29531
29532    // Fold second buffer - should remove selections from folded buffer
29533    cx.update_editor(|editor, _, cx| {
29534        editor.fold_buffer(buffer_ids[1], cx);
29535    });
29536    cx.assert_excerpts_with_selections(indoc! {"
29537        [EXCERPT]
29538        1
2953929540        3
29541        [EXCERPT]
29542        [FOLDED]
29543        "});
29544
29545    // Insert text - should only affect first buffer
29546    cx.update_editor(|editor, window, cx| {
29547        editor.handle_input("Y", window, cx);
29548    });
29549    cx.update_editor(|editor, _, cx| {
29550        editor.unfold_buffer(buffer_ids[1], cx);
29551    });
29552    cx.assert_excerpts_with_selections(indoc! {"
29553        [EXCERPT]
29554        1
2955529556        3
29557        [EXCERPT]
29558        1
29559        2
29560        3
29561        "});
29562
29563    // Scenario 3: Select "2", then fold first buffer before insertion
29564    cx.update_multibuffer(|mb, cx| {
29565        for buffer_id in buffer_ids.iter() {
29566            let buffer = mb.buffer(*buffer_id).unwrap();
29567            buffer.update(cx, |buffer, cx| {
29568                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29569            });
29570        }
29571    });
29572
29573    // Select "2" and select all matches
29574    cx.update_editor(|editor, window, cx| {
29575        editor.change_selections(None.into(), window, cx, |s| {
29576            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29577        });
29578        editor
29579            .select_all_matches(&SelectAllMatches, window, cx)
29580            .unwrap();
29581    });
29582
29583    // Fold first buffer - should remove selections from folded buffer
29584    cx.update_editor(|editor, _, cx| {
29585        editor.fold_buffer(buffer_ids[0], cx);
29586    });
29587    cx.assert_excerpts_with_selections(indoc! {"
29588        [EXCERPT]
29589        [FOLDED]
29590        [EXCERPT]
29591        1
2959229593        3
29594        "});
29595
29596    // Insert text - should only affect second buffer
29597    cx.update_editor(|editor, window, cx| {
29598        editor.handle_input("Z", window, cx);
29599    });
29600    cx.update_editor(|editor, _, cx| {
29601        editor.unfold_buffer(buffer_ids[0], cx);
29602    });
29603    cx.assert_excerpts_with_selections(indoc! {"
29604        [EXCERPT]
29605        1
29606        2
29607        3
29608        [EXCERPT]
29609        1
2961029611        3
29612        "});
29613
29614    // Test correct folded header is selected upon fold
29615    cx.update_editor(|editor, _, cx| {
29616        editor.fold_buffer(buffer_ids[0], cx);
29617        editor.fold_buffer(buffer_ids[1], cx);
29618    });
29619    cx.assert_excerpts_with_selections(indoc! {"
29620        [EXCERPT]
29621        [FOLDED]
29622        [EXCERPT]
29623        ˇ[FOLDED]
29624        "});
29625
29626    // Test selection inside folded buffer unfolds it on type
29627    cx.update_editor(|editor, window, cx| {
29628        editor.handle_input("W", window, cx);
29629    });
29630    cx.update_editor(|editor, _, cx| {
29631        editor.unfold_buffer(buffer_ids[0], cx);
29632    });
29633    cx.assert_excerpts_with_selections(indoc! {"
29634        [EXCERPT]
29635        1
29636        2
29637        3
29638        [EXCERPT]
29639        Wˇ1
29640        Z
29641        3
29642        "});
29643}
29644
29645#[gpui::test]
29646async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29647    init_test(cx, |_| {});
29648
29649    let (editor, cx) = cx.add_window_view(|window, cx| {
29650        let multi_buffer = MultiBuffer::build_multi(
29651            [
29652                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29653                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29654            ],
29655            cx,
29656        );
29657        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29658    });
29659
29660    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29661
29662    cx.assert_excerpts_with_selections(indoc! {"
29663        [EXCERPT]
29664        ˇ1
29665        2
29666        3
29667        [EXCERPT]
29668        1
29669        2
29670        3
29671        4
29672        5
29673        6
29674        7
29675        8
29676        9
29677        "});
29678
29679    cx.update_editor(|editor, window, cx| {
29680        editor.change_selections(None.into(), window, cx, |s| {
29681            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29682        });
29683    });
29684
29685    cx.assert_excerpts_with_selections(indoc! {"
29686        [EXCERPT]
29687        1
29688        2
29689        3
29690        [EXCERPT]
29691        1
29692        2
29693        3
29694        4
29695        5
29696        6
29697        ˇ7
29698        8
29699        9
29700        "});
29701
29702    cx.update_editor(|editor, _window, cx| {
29703        editor.set_vertical_scroll_margin(0, cx);
29704    });
29705
29706    cx.update_editor(|editor, window, cx| {
29707        assert_eq!(editor.vertical_scroll_margin(), 0);
29708        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29709        assert_eq!(
29710            editor.snapshot(window, cx).scroll_position(),
29711            gpui::Point::new(0., 12.0)
29712        );
29713    });
29714
29715    cx.update_editor(|editor, _window, cx| {
29716        editor.set_vertical_scroll_margin(3, cx);
29717    });
29718
29719    cx.update_editor(|editor, window, cx| {
29720        assert_eq!(editor.vertical_scroll_margin(), 3);
29721        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29722        assert_eq!(
29723            editor.snapshot(window, cx).scroll_position(),
29724            gpui::Point::new(0., 9.0)
29725        );
29726    });
29727}
29728
29729#[gpui::test]
29730async fn test_find_references_single_case(cx: &mut TestAppContext) {
29731    init_test(cx, |_| {});
29732    let mut cx = EditorLspTestContext::new_rust(
29733        lsp::ServerCapabilities {
29734            references_provider: Some(lsp::OneOf::Left(true)),
29735            ..lsp::ServerCapabilities::default()
29736        },
29737        cx,
29738    )
29739    .await;
29740
29741    let before = indoc!(
29742        r#"
29743        fn main() {
29744            let aˇbc = 123;
29745            let xyz = abc;
29746        }
29747        "#
29748    );
29749    let after = indoc!(
29750        r#"
29751        fn main() {
29752            let abc = 123;
29753            let xyz = ˇabc;
29754        }
29755        "#
29756    );
29757
29758    cx.lsp
29759        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29760            Ok(Some(vec![
29761                lsp::Location {
29762                    uri: params.text_document_position.text_document.uri.clone(),
29763                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29764                },
29765                lsp::Location {
29766                    uri: params.text_document_position.text_document.uri,
29767                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29768                },
29769            ]))
29770        });
29771
29772    cx.set_state(before);
29773
29774    let action = FindAllReferences {
29775        always_open_multibuffer: false,
29776    };
29777
29778    let navigated = cx
29779        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29780        .expect("should have spawned a task")
29781        .await
29782        .unwrap();
29783
29784    assert_eq!(navigated, Navigated::No);
29785
29786    cx.run_until_parked();
29787
29788    cx.assert_editor_state(after);
29789}
29790
29791#[gpui::test]
29792async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29793    init_test(cx, |settings| {
29794        settings.defaults.tab_size = Some(2.try_into().unwrap());
29795    });
29796
29797    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29798    let mut cx = EditorTestContext::new(cx).await;
29799    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29800
29801    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29802    cx.set_state(indoc! {"
29803        - [ ] taskˇ
29804    "});
29805    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29806    cx.wait_for_autoindent_applied().await;
29807    cx.assert_editor_state(indoc! {"
29808        - [ ] task
29809        - [ ] ˇ
29810    "});
29811
29812    // Case 2: Works with checked task items too
29813    cx.set_state(indoc! {"
29814        - [x] completed taskˇ
29815    "});
29816    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29817    cx.wait_for_autoindent_applied().await;
29818    cx.assert_editor_state(indoc! {"
29819        - [x] completed task
29820        - [ ] ˇ
29821    "});
29822
29823    // Case 2.1: Works with uppercase checked marker too
29824    cx.set_state(indoc! {"
29825        - [X] completed taskˇ
29826    "});
29827    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29828    cx.wait_for_autoindent_applied().await;
29829    cx.assert_editor_state(indoc! {"
29830        - [X] completed task
29831        - [ ] ˇ
29832    "});
29833
29834    // Case 3: Cursor position doesn't matter - content after marker is what counts
29835    cx.set_state(indoc! {"
29836        - [ ] taˇsk
29837    "});
29838    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29839    cx.wait_for_autoindent_applied().await;
29840    cx.assert_editor_state(indoc! {"
29841        - [ ] ta
29842        - [ ] ˇsk
29843    "});
29844
29845    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29846    cx.set_state(indoc! {"
29847        - [ ]  ˇ
29848    "});
29849    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29850    cx.wait_for_autoindent_applied().await;
29851    cx.assert_editor_state(
29852        indoc! {"
29853        - [ ]$$
29854        ˇ
29855    "}
29856        .replace("$", " ")
29857        .as_str(),
29858    );
29859
29860    // Case 5: Adding newline with content adds marker preserving indentation
29861    cx.set_state(indoc! {"
29862        - [ ] task
29863          - [ ] indentedˇ
29864    "});
29865    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29866    cx.wait_for_autoindent_applied().await;
29867    cx.assert_editor_state(indoc! {"
29868        - [ ] task
29869          - [ ] indented
29870          - [ ] ˇ
29871    "});
29872
29873    // Case 6: Adding newline with cursor right after prefix, unindents
29874    cx.set_state(indoc! {"
29875        - [ ] task
29876          - [ ] sub task
29877            - [ ] ˇ
29878    "});
29879    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29880    cx.wait_for_autoindent_applied().await;
29881    cx.assert_editor_state(indoc! {"
29882        - [ ] task
29883          - [ ] sub task
29884          - [ ] ˇ
29885    "});
29886    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29887    cx.wait_for_autoindent_applied().await;
29888
29889    // Case 7: Adding newline with cursor right after prefix, removes marker
29890    cx.assert_editor_state(indoc! {"
29891        - [ ] task
29892          - [ ] sub task
29893        - [ ] ˇ
29894    "});
29895    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29896    cx.wait_for_autoindent_applied().await;
29897    cx.assert_editor_state(indoc! {"
29898        - [ ] task
29899          - [ ] sub task
29900        ˇ
29901    "});
29902
29903    // Case 8: Cursor before or inside prefix does not add marker
29904    cx.set_state(indoc! {"
29905        ˇ- [ ] task
29906    "});
29907    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29908    cx.wait_for_autoindent_applied().await;
29909    cx.assert_editor_state(indoc! {"
29910
29911        ˇ- [ ] task
29912    "});
29913
29914    cx.set_state(indoc! {"
29915        - [ˇ ] task
29916    "});
29917    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29918    cx.wait_for_autoindent_applied().await;
29919    cx.assert_editor_state(indoc! {"
29920        - [
29921        ˇ
29922        ] task
29923    "});
29924}
29925
29926#[gpui::test]
29927async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29928    init_test(cx, |settings| {
29929        settings.defaults.tab_size = Some(2.try_into().unwrap());
29930    });
29931
29932    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29933    let mut cx = EditorTestContext::new(cx).await;
29934    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29935
29936    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29937    cx.set_state(indoc! {"
29938        - itemˇ
29939    "});
29940    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29941    cx.wait_for_autoindent_applied().await;
29942    cx.assert_editor_state(indoc! {"
29943        - item
29944        - ˇ
29945    "});
29946
29947    // Case 2: Works with different markers
29948    cx.set_state(indoc! {"
29949        * starred itemˇ
29950    "});
29951    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29952    cx.wait_for_autoindent_applied().await;
29953    cx.assert_editor_state(indoc! {"
29954        * starred item
29955        * ˇ
29956    "});
29957
29958    cx.set_state(indoc! {"
29959        + plus itemˇ
29960    "});
29961    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29962    cx.wait_for_autoindent_applied().await;
29963    cx.assert_editor_state(indoc! {"
29964        + plus item
29965        + ˇ
29966    "});
29967
29968    // Case 3: Cursor position doesn't matter - content after marker is what counts
29969    cx.set_state(indoc! {"
29970        - itˇem
29971    "});
29972    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29973    cx.wait_for_autoindent_applied().await;
29974    cx.assert_editor_state(indoc! {"
29975        - it
29976        - ˇem
29977    "});
29978
29979    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29980    cx.set_state(indoc! {"
29981        -  ˇ
29982    "});
29983    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29984    cx.wait_for_autoindent_applied().await;
29985    cx.assert_editor_state(
29986        indoc! {"
29987        - $
29988        ˇ
29989    "}
29990        .replace("$", " ")
29991        .as_str(),
29992    );
29993
29994    // Case 5: Adding newline with content adds marker preserving indentation
29995    cx.set_state(indoc! {"
29996        - item
29997          - indentedˇ
29998    "});
29999    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30000    cx.wait_for_autoindent_applied().await;
30001    cx.assert_editor_state(indoc! {"
30002        - item
30003          - indented
30004          - ˇ
30005    "});
30006
30007    // Case 6: Adding newline with cursor right after marker, unindents
30008    cx.set_state(indoc! {"
30009        - item
30010          - sub item
30011            - ˇ
30012    "});
30013    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30014    cx.wait_for_autoindent_applied().await;
30015    cx.assert_editor_state(indoc! {"
30016        - item
30017          - sub item
30018          - ˇ
30019    "});
30020    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30021    cx.wait_for_autoindent_applied().await;
30022
30023    // Case 7: Adding newline with cursor right after marker, removes marker
30024    cx.assert_editor_state(indoc! {"
30025        - item
30026          - sub item
30027        - ˇ
30028    "});
30029    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30030    cx.wait_for_autoindent_applied().await;
30031    cx.assert_editor_state(indoc! {"
30032        - item
30033          - sub item
30034        ˇ
30035    "});
30036
30037    // Case 8: Cursor before or inside prefix does not add marker
30038    cx.set_state(indoc! {"
30039        ˇ- item
30040    "});
30041    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30042    cx.wait_for_autoindent_applied().await;
30043    cx.assert_editor_state(indoc! {"
30044
30045        ˇ- item
30046    "});
30047
30048    cx.set_state(indoc! {"
30049        -ˇ item
30050    "});
30051    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30052    cx.wait_for_autoindent_applied().await;
30053    cx.assert_editor_state(indoc! {"
30054        -
30055        ˇitem
30056    "});
30057}
30058
30059#[gpui::test]
30060async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
30061    init_test(cx, |settings| {
30062        settings.defaults.tab_size = Some(2.try_into().unwrap());
30063    });
30064
30065    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30066    let mut cx = EditorTestContext::new(cx).await;
30067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30068
30069    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30070    cx.set_state(indoc! {"
30071        1. first itemˇ
30072    "});
30073    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30074    cx.wait_for_autoindent_applied().await;
30075    cx.assert_editor_state(indoc! {"
30076        1. first item
30077        2. ˇ
30078    "});
30079
30080    // Case 2: Works with larger numbers
30081    cx.set_state(indoc! {"
30082        10. tenth itemˇ
30083    "});
30084    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30085    cx.wait_for_autoindent_applied().await;
30086    cx.assert_editor_state(indoc! {"
30087        10. tenth item
30088        11. ˇ
30089    "});
30090
30091    // Case 3: Cursor position doesn't matter - content after marker is what counts
30092    cx.set_state(indoc! {"
30093        1. itˇem
30094    "});
30095    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30096    cx.wait_for_autoindent_applied().await;
30097    cx.assert_editor_state(indoc! {"
30098        1. it
30099        2. ˇem
30100    "});
30101
30102    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
30103    cx.set_state(indoc! {"
30104        1.  ˇ
30105    "});
30106    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30107    cx.wait_for_autoindent_applied().await;
30108    cx.assert_editor_state(
30109        indoc! {"
30110        1. $
30111        ˇ
30112    "}
30113        .replace("$", " ")
30114        .as_str(),
30115    );
30116
30117    // Case 5: Adding newline with content adds marker preserving indentation
30118    cx.set_state(indoc! {"
30119        1. item
30120          2. indentedˇ
30121    "});
30122    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30123    cx.wait_for_autoindent_applied().await;
30124    cx.assert_editor_state(indoc! {"
30125        1. item
30126          2. indented
30127          3. ˇ
30128    "});
30129
30130    // Case 6: Adding newline with cursor right after marker, unindents
30131    cx.set_state(indoc! {"
30132        1. item
30133          2. sub item
30134            3. ˇ
30135    "});
30136    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30137    cx.wait_for_autoindent_applied().await;
30138    cx.assert_editor_state(indoc! {"
30139        1. item
30140          2. sub item
30141          1. ˇ
30142    "});
30143    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30144    cx.wait_for_autoindent_applied().await;
30145
30146    // Case 7: Adding newline with cursor right after marker, removes marker
30147    cx.assert_editor_state(indoc! {"
30148        1. item
30149          2. sub item
30150        1. ˇ
30151    "});
30152    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30153    cx.wait_for_autoindent_applied().await;
30154    cx.assert_editor_state(indoc! {"
30155        1. item
30156          2. sub item
30157        ˇ
30158    "});
30159
30160    // Case 8: Cursor before or inside prefix does not add marker
30161    cx.set_state(indoc! {"
30162        ˇ1. item
30163    "});
30164    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30165    cx.wait_for_autoindent_applied().await;
30166    cx.assert_editor_state(indoc! {"
30167
30168        ˇ1. item
30169    "});
30170
30171    cx.set_state(indoc! {"
30172        1ˇ. item
30173    "});
30174    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30175    cx.wait_for_autoindent_applied().await;
30176    cx.assert_editor_state(indoc! {"
30177        1
30178        ˇ. item
30179    "});
30180}
30181
30182#[gpui::test]
30183async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
30184    init_test(cx, |settings| {
30185        settings.defaults.tab_size = Some(2.try_into().unwrap());
30186    });
30187
30188    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30189    let mut cx = EditorTestContext::new(cx).await;
30190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30191
30192    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30193    cx.set_state(indoc! {"
30194        1. first item
30195          1. sub first item
30196          2. sub second item
30197          3. ˇ
30198    "});
30199    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30200    cx.wait_for_autoindent_applied().await;
30201    cx.assert_editor_state(indoc! {"
30202        1. first item
30203          1. sub first item
30204          2. sub second item
30205        1. ˇ
30206    "});
30207}
30208
30209#[gpui::test]
30210async fn test_tab_list_indent(cx: &mut TestAppContext) {
30211    init_test(cx, |settings| {
30212        settings.defaults.tab_size = Some(2.try_into().unwrap());
30213    });
30214
30215    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30216    let mut cx = EditorTestContext::new(cx).await;
30217    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30218
30219    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30220    cx.set_state(indoc! {"
30221        - ˇitem
30222    "});
30223    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30224    cx.wait_for_autoindent_applied().await;
30225    let expected = indoc! {"
30226        $$- ˇitem
30227    "};
30228    cx.assert_editor_state(expected.replace("$", " ").as_str());
30229
30230    // Case 2: Task list - cursor after prefix
30231    cx.set_state(indoc! {"
30232        - [ ] ˇtask
30233    "});
30234    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30235    cx.wait_for_autoindent_applied().await;
30236    let expected = indoc! {"
30237        $$- [ ] ˇtask
30238    "};
30239    cx.assert_editor_state(expected.replace("$", " ").as_str());
30240
30241    // Case 3: Ordered list - cursor after prefix
30242    cx.set_state(indoc! {"
30243        1. ˇfirst
30244    "});
30245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30246    cx.wait_for_autoindent_applied().await;
30247    let expected = indoc! {"
30248        $$1. ˇfirst
30249    "};
30250    cx.assert_editor_state(expected.replace("$", " ").as_str());
30251
30252    // Case 4: With existing indentation - adds more indent
30253    let initial = indoc! {"
30254        $$- ˇitem
30255    "};
30256    cx.set_state(initial.replace("$", " ").as_str());
30257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30258    cx.wait_for_autoindent_applied().await;
30259    let expected = indoc! {"
30260        $$$$- ˇitem
30261    "};
30262    cx.assert_editor_state(expected.replace("$", " ").as_str());
30263
30264    // Case 5: Empty list item
30265    cx.set_state(indoc! {"
30266        - ˇ
30267    "});
30268    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30269    cx.wait_for_autoindent_applied().await;
30270    let expected = indoc! {"
30271        $$- ˇ
30272    "};
30273    cx.assert_editor_state(expected.replace("$", " ").as_str());
30274
30275    // Case 6: Cursor at end of line with content
30276    cx.set_state(indoc! {"
30277        - itemˇ
30278    "});
30279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30280    cx.wait_for_autoindent_applied().await;
30281    let expected = indoc! {"
30282        $$- itemˇ
30283    "};
30284    cx.assert_editor_state(expected.replace("$", " ").as_str());
30285
30286    // Case 7: Cursor at start of list item, indents it
30287    cx.set_state(indoc! {"
30288        - item
30289        ˇ  - sub item
30290    "});
30291    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30292    cx.wait_for_autoindent_applied().await;
30293    let expected = indoc! {"
30294        - item
30295          ˇ  - sub item
30296    "};
30297    cx.assert_editor_state(expected);
30298
30299    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30300    cx.update_editor(|_, _, cx| {
30301        SettingsStore::update_global(cx, |store, cx| {
30302            store.update_user_settings(cx, |settings| {
30303                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30304            });
30305        });
30306    });
30307    cx.set_state(indoc! {"
30308        - item
30309        ˇ  - sub item
30310    "});
30311    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30312    cx.wait_for_autoindent_applied().await;
30313    let expected = indoc! {"
30314        - item
30315          ˇ- sub item
30316    "};
30317    cx.assert_editor_state(expected);
30318}
30319
30320#[gpui::test]
30321async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30322    init_test(cx, |_| {});
30323    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
30324
30325    cx.update(|cx| {
30326        SettingsStore::update_global(cx, |store, cx| {
30327            store.update_user_settings(cx, |settings| {
30328                settings.project.all_languages.defaults.inlay_hints =
30329                    Some(InlayHintSettingsContent {
30330                        enabled: Some(true),
30331                        ..InlayHintSettingsContent::default()
30332                    });
30333            });
30334        });
30335    });
30336
30337    let fs = FakeFs::new(cx.executor());
30338    fs.insert_tree(
30339        path!("/project"),
30340        json!({
30341            ".zed": {
30342                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30343            },
30344            "main.rs": "fn main() {}"
30345        }),
30346    )
30347    .await;
30348
30349    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30350    let server_name = "override-rust-analyzer";
30351    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30352
30353    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30354    language_registry.add(rust_lang());
30355
30356    let capabilities = lsp::ServerCapabilities {
30357        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30358        ..lsp::ServerCapabilities::default()
30359    };
30360    let mut fake_language_servers = language_registry.register_fake_lsp(
30361        "Rust",
30362        FakeLspAdapter {
30363            name: server_name,
30364            capabilities,
30365            initializer: Some(Box::new({
30366                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30367                move |fake_server| {
30368                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30369                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30370                        move |_params, _| {
30371                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30372                            async move {
30373                                Ok(Some(vec![lsp::InlayHint {
30374                                    position: lsp::Position::new(0, 0),
30375                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30376                                    kind: None,
30377                                    text_edits: None,
30378                                    tooltip: None,
30379                                    padding_left: None,
30380                                    padding_right: None,
30381                                    data: None,
30382                                }]))
30383                            }
30384                        },
30385                    );
30386                }
30387            })),
30388            ..FakeLspAdapter::default()
30389        },
30390    );
30391
30392    cx.run_until_parked();
30393
30394    let worktree_id = project.read_with(cx, |project, cx| {
30395        project
30396            .worktrees(cx)
30397            .next()
30398            .map(|wt| wt.read(cx).id())
30399            .expect("should have a worktree")
30400    });
30401    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30402
30403    let trusted_worktrees =
30404        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30405
30406    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30407        store.can_trust(&worktree_store, worktree_id, cx)
30408    });
30409    assert!(!can_trust, "worktree should be restricted initially");
30410
30411    let buffer_before_approval = project
30412        .update(cx, |project, cx| {
30413            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30414        })
30415        .await
30416        .unwrap();
30417
30418    let (editor, cx) = cx.add_window_view(|window, cx| {
30419        Editor::new(
30420            EditorMode::full(),
30421            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30422            Some(project.clone()),
30423            window,
30424            cx,
30425        )
30426    });
30427    cx.run_until_parked();
30428    let fake_language_server = fake_language_servers.next();
30429
30430    cx.read(|cx| {
30431        let file = buffer_before_approval.read(cx).file();
30432        assert_eq!(
30433            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30434                .language_servers,
30435            ["...".to_string()],
30436            "local .zed/settings.json must not apply before trust approval"
30437        )
30438    });
30439
30440    editor.update_in(cx, |editor, window, cx| {
30441        editor.handle_input("1", window, cx);
30442    });
30443    cx.run_until_parked();
30444    cx.executor()
30445        .advance_clock(std::time::Duration::from_secs(1));
30446    assert_eq!(
30447        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30448        0,
30449        "inlay hints must not be queried before trust approval"
30450    );
30451
30452    trusted_worktrees.update(cx, |store, cx| {
30453        store.trust(
30454            &worktree_store,
30455            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30456            cx,
30457        );
30458    });
30459    cx.run_until_parked();
30460
30461    cx.read(|cx| {
30462        let file = buffer_before_approval.read(cx).file();
30463        assert_eq!(
30464            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30465                .language_servers,
30466            ["override-rust-analyzer".to_string()],
30467            "local .zed/settings.json should apply after trust approval"
30468        )
30469    });
30470    let _fake_language_server = fake_language_server.await.unwrap();
30471    editor.update_in(cx, |editor, window, cx| {
30472        editor.handle_input("1", window, cx);
30473    });
30474    cx.run_until_parked();
30475    cx.executor()
30476        .advance_clock(std::time::Duration::from_secs(1));
30477    assert!(
30478        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30479        "inlay hints should be queried after trust approval"
30480    );
30481
30482    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30483        store.can_trust(&worktree_store, worktree_id, cx)
30484    });
30485    assert!(can_trust_after, "worktree should be trusted after trust()");
30486}
30487
30488#[gpui::test]
30489fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30490    // This test reproduces a bug where drawing an editor at a position above the viewport
30491    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30492    // causes an infinite loop in blocks_in_range.
30493    //
30494    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30495    // the content mask intersection produces visible_bounds with origin at the viewport top.
30496    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30497    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30498    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30499    init_test(cx, |_| {});
30500
30501    let window = cx.add_window(|_, _| gpui::Empty);
30502    let mut cx = VisualTestContext::from_window(*window, cx);
30503
30504    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30505    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30506
30507    // Simulate a small viewport (500x500 pixels at origin 0,0)
30508    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30509
30510    // Draw the editor at a very negative Y position, simulating an editor that's been
30511    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30512    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30513    // This should NOT hang - it should just render nothing.
30514    cx.draw(
30515        gpui::point(px(0.), px(-10000.)),
30516        gpui::size(px(500.), px(3000.)),
30517        |_, _| editor.clone().into_any_element(),
30518    );
30519
30520    // If we get here without hanging, the test passes
30521}
30522
30523#[gpui::test]
30524async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30525    init_test(cx, |_| {});
30526
30527    let fs = FakeFs::new(cx.executor());
30528    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30529        .await;
30530
30531    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30532    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30533    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30534
30535    let editor = workspace
30536        .update(cx, |workspace, window, cx| {
30537            workspace.open_abs_path(
30538                PathBuf::from(path!("/root/file.txt")),
30539                OpenOptions::default(),
30540                window,
30541                cx,
30542            )
30543        })
30544        .unwrap()
30545        .await
30546        .unwrap()
30547        .downcast::<Editor>()
30548        .unwrap();
30549
30550    // Enable diff review button mode
30551    editor.update(cx, |editor, cx| {
30552        editor.set_show_diff_review_button(true, cx);
30553    });
30554
30555    // Initially, no indicator should be present
30556    editor.update(cx, |editor, _cx| {
30557        assert!(
30558            editor.gutter_diff_review_indicator.0.is_none(),
30559            "Indicator should be None initially"
30560        );
30561    });
30562}
30563
30564#[gpui::test]
30565async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
30566    init_test(cx, |_| {});
30567
30568    // Register DisableAiSettings and set disable_ai to true
30569    cx.update(|cx| {
30570        project::DisableAiSettings::register(cx);
30571        project::DisableAiSettings::override_global(
30572            project::DisableAiSettings { disable_ai: true },
30573            cx,
30574        );
30575    });
30576
30577    let fs = FakeFs::new(cx.executor());
30578    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30579        .await;
30580
30581    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30582    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30583    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30584
30585    let editor = workspace
30586        .update(cx, |workspace, window, cx| {
30587            workspace.open_abs_path(
30588                PathBuf::from(path!("/root/file.txt")),
30589                OpenOptions::default(),
30590                window,
30591                cx,
30592            )
30593        })
30594        .unwrap()
30595        .await
30596        .unwrap()
30597        .downcast::<Editor>()
30598        .unwrap();
30599
30600    // Enable diff review button mode
30601    editor.update(cx, |editor, cx| {
30602        editor.set_show_diff_review_button(true, cx);
30603    });
30604
30605    // Verify AI is disabled
30606    cx.read(|cx| {
30607        assert!(
30608            project::DisableAiSettings::get_global(cx).disable_ai,
30609            "AI should be disabled"
30610        );
30611    });
30612
30613    // The indicator should not be created when AI is disabled
30614    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
30615    editor.update(cx, |editor, _cx| {
30616        assert!(
30617            editor.gutter_diff_review_indicator.0.is_none(),
30618            "Indicator should be None when AI is disabled"
30619        );
30620    });
30621}
30622
30623#[gpui::test]
30624async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
30625    init_test(cx, |_| {});
30626
30627    // Register DisableAiSettings and set disable_ai to false
30628    cx.update(|cx| {
30629        project::DisableAiSettings::register(cx);
30630        project::DisableAiSettings::override_global(
30631            project::DisableAiSettings { disable_ai: false },
30632            cx,
30633        );
30634    });
30635
30636    let fs = FakeFs::new(cx.executor());
30637    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30638        .await;
30639
30640    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30641    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30642    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30643
30644    let editor = workspace
30645        .update(cx, |workspace, window, cx| {
30646            workspace.open_abs_path(
30647                PathBuf::from(path!("/root/file.txt")),
30648                OpenOptions::default(),
30649                window,
30650                cx,
30651            )
30652        })
30653        .unwrap()
30654        .await
30655        .unwrap()
30656        .downcast::<Editor>()
30657        .unwrap();
30658
30659    // Enable diff review button mode
30660    editor.update(cx, |editor, cx| {
30661        editor.set_show_diff_review_button(true, cx);
30662    });
30663
30664    // Verify AI is enabled
30665    cx.read(|cx| {
30666        assert!(
30667            !project::DisableAiSettings::get_global(cx).disable_ai,
30668            "AI should be enabled"
30669        );
30670    });
30671
30672    // The show_diff_review_button flag should be true
30673    editor.update(cx, |editor, _cx| {
30674        assert!(
30675            editor.show_diff_review_button(),
30676            "show_diff_review_button should be true"
30677        );
30678    });
30679}
30680
30681/// Helper function to create a DiffHunkKey for testing.
30682/// Uses Anchor::min() as a placeholder anchor since these tests don't need
30683/// real buffer positioning.
30684fn test_hunk_key(file_path: &str) -> DiffHunkKey {
30685    DiffHunkKey {
30686        file_path: if file_path.is_empty() {
30687            Arc::from(util::rel_path::RelPath::empty())
30688        } else {
30689            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30690        },
30691        hunk_start_anchor: Anchor::min(),
30692    }
30693}
30694
30695/// Helper function to create a DiffHunkKey with a specific anchor for testing.
30696fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
30697    DiffHunkKey {
30698        file_path: if file_path.is_empty() {
30699            Arc::from(util::rel_path::RelPath::empty())
30700        } else {
30701            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30702        },
30703        hunk_start_anchor: anchor,
30704    }
30705}
30706
30707/// Helper function to add a review comment with default anchors for testing.
30708fn add_test_comment(
30709    editor: &mut Editor,
30710    key: DiffHunkKey,
30711    comment: &str,
30712    cx: &mut Context<Editor>,
30713) -> usize {
30714    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
30715}
30716
30717#[gpui::test]
30718fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
30719    init_test(cx, |_| {});
30720
30721    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30722
30723    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30724        let key = test_hunk_key("");
30725
30726        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
30727
30728        let snapshot = editor.buffer().read(cx).snapshot(cx);
30729        assert_eq!(editor.total_review_comment_count(), 1);
30730        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
30731
30732        let comments = editor.comments_for_hunk(&key, &snapshot);
30733        assert_eq!(comments.len(), 1);
30734        assert_eq!(comments[0].comment, "Test comment");
30735        assert_eq!(comments[0].id, id);
30736    });
30737}
30738
30739#[gpui::test]
30740fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
30741    init_test(cx, |_| {});
30742
30743    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30744
30745    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30746        let snapshot = editor.buffer().read(cx).snapshot(cx);
30747        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30748        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30749        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30750        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30751
30752        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
30753        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
30754
30755        let snapshot = editor.buffer().read(cx).snapshot(cx);
30756        assert_eq!(editor.total_review_comment_count(), 2);
30757        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
30758        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
30759
30760        assert_eq!(
30761            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
30762            "Comment for file1"
30763        );
30764        assert_eq!(
30765            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
30766            "Comment for file2"
30767        );
30768    });
30769}
30770
30771#[gpui::test]
30772fn test_review_comment_remove(cx: &mut TestAppContext) {
30773    init_test(cx, |_| {});
30774
30775    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30776
30777    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30778        let key = test_hunk_key("");
30779
30780        let id = add_test_comment(editor, key, "To be removed", cx);
30781
30782        assert_eq!(editor.total_review_comment_count(), 1);
30783
30784        let removed = editor.remove_review_comment(id, cx);
30785        assert!(removed);
30786        assert_eq!(editor.total_review_comment_count(), 0);
30787
30788        // Try to remove again
30789        let removed_again = editor.remove_review_comment(id, cx);
30790        assert!(!removed_again);
30791    });
30792}
30793
30794#[gpui::test]
30795fn test_review_comment_update(cx: &mut TestAppContext) {
30796    init_test(cx, |_| {});
30797
30798    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30799
30800    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30801        let key = test_hunk_key("");
30802
30803        let id = add_test_comment(editor, key.clone(), "Original text", cx);
30804
30805        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
30806        assert!(updated);
30807
30808        let snapshot = editor.buffer().read(cx).snapshot(cx);
30809        let comments = editor.comments_for_hunk(&key, &snapshot);
30810        assert_eq!(comments[0].comment, "Updated text");
30811        assert!(!comments[0].is_editing); // Should clear editing flag
30812    });
30813}
30814
30815#[gpui::test]
30816fn test_review_comment_take_all(cx: &mut TestAppContext) {
30817    init_test(cx, |_| {});
30818
30819    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30820
30821    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30822        let snapshot = editor.buffer().read(cx).snapshot(cx);
30823        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30824        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30825        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30826        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30827
30828        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
30829        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
30830        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
30831
30832        // IDs should be sequential starting from 0
30833        assert_eq!(id1, 0);
30834        assert_eq!(id2, 1);
30835        assert_eq!(id3, 2);
30836
30837        assert_eq!(editor.total_review_comment_count(), 3);
30838
30839        let taken = editor.take_all_review_comments(cx);
30840
30841        // Should have 2 entries (one per hunk)
30842        assert_eq!(taken.len(), 2);
30843
30844        // Total comments should be 3
30845        let total: usize = taken
30846            .iter()
30847            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
30848            .sum();
30849        assert_eq!(total, 3);
30850
30851        // Storage should be empty
30852        assert_eq!(editor.total_review_comment_count(), 0);
30853
30854        // After taking all comments, ID counter should reset
30855        // New comments should get IDs starting from 0 again
30856        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
30857        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
30858
30859        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
30860        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
30861    });
30862}
30863
30864#[gpui::test]
30865fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
30866    init_test(cx, |_| {});
30867
30868    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30869
30870    // Show overlay
30871    editor
30872        .update(cx, |editor, window, cx| {
30873            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30874        })
30875        .unwrap();
30876
30877    // Verify overlay is shown
30878    editor
30879        .update(cx, |editor, _window, cx| {
30880            assert!(!editor.diff_review_overlays.is_empty());
30881            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
30882            assert!(editor.diff_review_prompt_editor().is_some());
30883        })
30884        .unwrap();
30885
30886    // Dismiss overlay
30887    editor
30888        .update(cx, |editor, _window, cx| {
30889            editor.dismiss_all_diff_review_overlays(cx);
30890        })
30891        .unwrap();
30892
30893    // Verify overlay is dismissed
30894    editor
30895        .update(cx, |editor, _window, cx| {
30896            assert!(editor.diff_review_overlays.is_empty());
30897            assert_eq!(editor.diff_review_line_range(cx), None);
30898            assert!(editor.diff_review_prompt_editor().is_none());
30899        })
30900        .unwrap();
30901}
30902
30903#[gpui::test]
30904fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
30905    init_test(cx, |_| {});
30906
30907    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30908
30909    // Show overlay
30910    editor
30911        .update(cx, |editor, window, cx| {
30912            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30913        })
30914        .unwrap();
30915
30916    // Verify overlay is shown
30917    editor
30918        .update(cx, |editor, _window, _cx| {
30919            assert!(!editor.diff_review_overlays.is_empty());
30920        })
30921        .unwrap();
30922
30923    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
30924    editor
30925        .update(cx, |editor, window, cx| {
30926            editor.dismiss_menus_and_popups(true, window, cx);
30927        })
30928        .unwrap();
30929
30930    // Verify overlay is dismissed
30931    editor
30932        .update(cx, |editor, _window, _cx| {
30933            assert!(editor.diff_review_overlays.is_empty());
30934        })
30935        .unwrap();
30936}
30937
30938#[gpui::test]
30939fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
30940    init_test(cx, |_| {});
30941
30942    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30943
30944    // Show overlay
30945    editor
30946        .update(cx, |editor, window, cx| {
30947            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30948        })
30949        .unwrap();
30950
30951    // Try to submit without typing anything (empty comment)
30952    editor
30953        .update(cx, |editor, window, cx| {
30954            editor.submit_diff_review_comment(window, cx);
30955        })
30956        .unwrap();
30957
30958    // Verify no comment was added
30959    editor
30960        .update(cx, |editor, _window, _cx| {
30961            assert_eq!(editor.total_review_comment_count(), 0);
30962        })
30963        .unwrap();
30964
30965    // Try to submit with whitespace-only comment
30966    editor
30967        .update(cx, |editor, window, cx| {
30968            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
30969                prompt_editor.update(cx, |pe, cx| {
30970                    pe.insert("   \n\t  ", window, cx);
30971                });
30972            }
30973            editor.submit_diff_review_comment(window, cx);
30974        })
30975        .unwrap();
30976
30977    // Verify still no comment was added
30978    editor
30979        .update(cx, |editor, _window, _cx| {
30980            assert_eq!(editor.total_review_comment_count(), 0);
30981        })
30982        .unwrap();
30983}
30984
30985#[gpui::test]
30986fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
30987    init_test(cx, |_| {});
30988
30989    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30990
30991    // Add a comment directly
30992    let comment_id = editor
30993        .update(cx, |editor, _window, cx| {
30994            let key = test_hunk_key("");
30995            add_test_comment(editor, key, "Original comment", cx)
30996        })
30997        .unwrap();
30998
30999    // Set comment to editing mode
31000    editor
31001        .update(cx, |editor, _window, cx| {
31002            editor.set_comment_editing(comment_id, true, cx);
31003        })
31004        .unwrap();
31005
31006    // Verify editing flag is set
31007    editor
31008        .update(cx, |editor, _window, cx| {
31009            let key = test_hunk_key("");
31010            let snapshot = editor.buffer().read(cx).snapshot(cx);
31011            let comments = editor.comments_for_hunk(&key, &snapshot);
31012            assert_eq!(comments.len(), 1);
31013            assert!(comments[0].is_editing);
31014        })
31015        .unwrap();
31016
31017    // Update the comment
31018    editor
31019        .update(cx, |editor, _window, cx| {
31020            let updated =
31021                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
31022            assert!(updated);
31023        })
31024        .unwrap();
31025
31026    // Verify comment was updated and editing flag is cleared
31027    editor
31028        .update(cx, |editor, _window, cx| {
31029            let key = test_hunk_key("");
31030            let snapshot = editor.buffer().read(cx).snapshot(cx);
31031            let comments = editor.comments_for_hunk(&key, &snapshot);
31032            assert_eq!(comments[0].comment, "Updated comment");
31033            assert!(!comments[0].is_editing);
31034        })
31035        .unwrap();
31036}
31037
31038#[gpui::test]
31039fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
31040    init_test(cx, |_| {});
31041
31042    // Create an editor with some text
31043    let editor = cx.add_window(|window, cx| {
31044        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31045        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31046        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31047    });
31048
31049    // Add a comment with an anchor on line 2
31050    editor
31051        .update(cx, |editor, _window, cx| {
31052            let snapshot = editor.buffer().read(cx).snapshot(cx);
31053            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31054            let key = DiffHunkKey {
31055                file_path: Arc::from(util::rel_path::RelPath::empty()),
31056                hunk_start_anchor: anchor,
31057            };
31058            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31059            assert_eq!(editor.total_review_comment_count(), 1);
31060        })
31061        .unwrap();
31062
31063    // Delete all content (this should orphan the comment's anchor)
31064    editor
31065        .update(cx, |editor, window, cx| {
31066            editor.select_all(&SelectAll, window, cx);
31067            editor.insert("completely new content", window, cx);
31068        })
31069        .unwrap();
31070
31071    // Trigger cleanup
31072    editor
31073        .update(cx, |editor, _window, cx| {
31074            editor.cleanup_orphaned_review_comments(cx);
31075            // Comment should be removed because its anchor is invalid
31076            assert_eq!(editor.total_review_comment_count(), 0);
31077        })
31078        .unwrap();
31079}
31080
31081#[gpui::test]
31082fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
31083    init_test(cx, |_| {});
31084
31085    // Create an editor with some text
31086    let editor = cx.add_window(|window, cx| {
31087        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31088        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31089        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31090    });
31091
31092    // Add a comment with an anchor on line 2
31093    editor
31094        .update(cx, |editor, _window, cx| {
31095            let snapshot = editor.buffer().read(cx).snapshot(cx);
31096            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31097            let key = DiffHunkKey {
31098                file_path: Arc::from(util::rel_path::RelPath::empty()),
31099                hunk_start_anchor: anchor,
31100            };
31101            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31102            assert_eq!(editor.total_review_comment_count(), 1);
31103        })
31104        .unwrap();
31105
31106    // Edit the buffer - this should trigger cleanup via on_buffer_event
31107    // Delete all content which orphans the anchor
31108    editor
31109        .update(cx, |editor, window, cx| {
31110            editor.select_all(&SelectAll, window, cx);
31111            editor.insert("completely new content", window, cx);
31112            // The cleanup is called automatically in on_buffer_event when Edited fires
31113        })
31114        .unwrap();
31115
31116    // Verify cleanup happened automatically (not manually triggered)
31117    editor
31118        .update(cx, |editor, _window, _cx| {
31119            // Comment should be removed because its anchor became invalid
31120            // and cleanup was called automatically on buffer edit
31121            assert_eq!(editor.total_review_comment_count(), 0);
31122        })
31123        .unwrap();
31124}
31125
31126#[gpui::test]
31127fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
31128    init_test(cx, |_| {});
31129
31130    // This test verifies that comments can be stored for multiple different hunks
31131    // and that hunk_comment_count correctly identifies comments per hunk.
31132    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31133
31134    _ = editor.update(cx, |editor, _window, cx| {
31135        let snapshot = editor.buffer().read(cx).snapshot(cx);
31136
31137        // Create two different hunk keys (simulating two different files)
31138        let anchor = snapshot.anchor_before(Point::new(0, 0));
31139        let key1 = DiffHunkKey {
31140            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
31141            hunk_start_anchor: anchor,
31142        };
31143        let key2 = DiffHunkKey {
31144            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
31145            hunk_start_anchor: anchor,
31146        };
31147
31148        // Add comments to first hunk
31149        editor.add_review_comment(
31150            key1.clone(),
31151            "Comment 1 for file1".to_string(),
31152            anchor..anchor,
31153            cx,
31154        );
31155        editor.add_review_comment(
31156            key1.clone(),
31157            "Comment 2 for file1".to_string(),
31158            anchor..anchor,
31159            cx,
31160        );
31161
31162        // Add comment to second hunk
31163        editor.add_review_comment(
31164            key2.clone(),
31165            "Comment for file2".to_string(),
31166            anchor..anchor,
31167            cx,
31168        );
31169
31170        // Verify total count
31171        assert_eq!(editor.total_review_comment_count(), 3);
31172
31173        // Verify per-hunk counts
31174        let snapshot = editor.buffer().read(cx).snapshot(cx);
31175        assert_eq!(
31176            editor.hunk_comment_count(&key1, &snapshot),
31177            2,
31178            "file1 should have 2 comments"
31179        );
31180        assert_eq!(
31181            editor.hunk_comment_count(&key2, &snapshot),
31182            1,
31183            "file2 should have 1 comment"
31184        );
31185
31186        // Verify comments_for_hunk returns correct comments
31187        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
31188        assert_eq!(file1_comments.len(), 2);
31189        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
31190        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
31191
31192        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
31193        assert_eq!(file2_comments.len(), 1);
31194        assert_eq!(file2_comments[0].comment, "Comment for file2");
31195    });
31196}
31197
31198#[gpui::test]
31199fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
31200    init_test(cx, |_| {});
31201
31202    // This test verifies that hunk_keys_match correctly identifies when two
31203    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
31204    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31205
31206    _ = editor.update(cx, |editor, _window, cx| {
31207        let snapshot = editor.buffer().read(cx).snapshot(cx);
31208        let anchor = snapshot.anchor_before(Point::new(0, 0));
31209
31210        // Create two keys with the same file path and anchor
31211        let key1 = DiffHunkKey {
31212            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31213            hunk_start_anchor: anchor,
31214        };
31215        let key2 = DiffHunkKey {
31216            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31217            hunk_start_anchor: anchor,
31218        };
31219
31220        // Add comment to first key
31221        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
31222
31223        // Verify second key (same hunk) finds the comment
31224        let snapshot = editor.buffer().read(cx).snapshot(cx);
31225        assert_eq!(
31226            editor.hunk_comment_count(&key2, &snapshot),
31227            1,
31228            "Same hunk should find the comment"
31229        );
31230
31231        // Create a key with different file path
31232        let different_file_key = DiffHunkKey {
31233            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
31234            hunk_start_anchor: anchor,
31235        };
31236
31237        // Different file should not find the comment
31238        assert_eq!(
31239            editor.hunk_comment_count(&different_file_key, &snapshot),
31240            0,
31241            "Different file should not find the comment"
31242        );
31243    });
31244}
31245
31246#[gpui::test]
31247fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
31248    init_test(cx, |_| {});
31249
31250    // This test verifies that set_diff_review_comments_expanded correctly
31251    // updates the expanded state of overlays.
31252    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31253
31254    // Show overlay
31255    editor
31256        .update(cx, |editor, window, cx| {
31257            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31258        })
31259        .unwrap();
31260
31261    // Verify initially expanded (default)
31262    editor
31263        .update(cx, |editor, _window, _cx| {
31264            assert!(
31265                editor.diff_review_overlays[0].comments_expanded,
31266                "Should be expanded by default"
31267            );
31268        })
31269        .unwrap();
31270
31271    // Set to collapsed using the public method
31272    editor
31273        .update(cx, |editor, _window, cx| {
31274            editor.set_diff_review_comments_expanded(false, cx);
31275        })
31276        .unwrap();
31277
31278    // Verify collapsed
31279    editor
31280        .update(cx, |editor, _window, _cx| {
31281            assert!(
31282                !editor.diff_review_overlays[0].comments_expanded,
31283                "Should be collapsed after setting to false"
31284            );
31285        })
31286        .unwrap();
31287
31288    // Set back to expanded
31289    editor
31290        .update(cx, |editor, _window, cx| {
31291            editor.set_diff_review_comments_expanded(true, cx);
31292        })
31293        .unwrap();
31294
31295    // Verify expanded again
31296    editor
31297        .update(cx, |editor, _window, _cx| {
31298            assert!(
31299                editor.diff_review_overlays[0].comments_expanded,
31300                "Should be expanded after setting to true"
31301            );
31302        })
31303        .unwrap();
31304}
31305
31306#[gpui::test]
31307fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
31308    init_test(cx, |_| {});
31309
31310    // Create an editor with multiple lines of text
31311    let editor = cx.add_window(|window, cx| {
31312        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
31313        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31314        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31315    });
31316
31317    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
31318    editor
31319        .update(cx, |editor, window, cx| {
31320            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
31321        })
31322        .unwrap();
31323
31324    // Verify line range
31325    editor
31326        .update(cx, |editor, _window, cx| {
31327            assert!(!editor.diff_review_overlays.is_empty());
31328            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
31329        })
31330        .unwrap();
31331
31332    // Dismiss and test with reversed range (end < start)
31333    editor
31334        .update(cx, |editor, _window, cx| {
31335            editor.dismiss_all_diff_review_overlays(cx);
31336        })
31337        .unwrap();
31338
31339    // Show overlay with reversed range - should normalize it
31340    editor
31341        .update(cx, |editor, window, cx| {
31342            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
31343        })
31344        .unwrap();
31345
31346    // Verify range is normalized (start <= end)
31347    editor
31348        .update(cx, |editor, _window, cx| {
31349            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31350        })
31351        .unwrap();
31352}
31353
31354#[gpui::test]
31355fn test_diff_review_drag_state(cx: &mut TestAppContext) {
31356    init_test(cx, |_| {});
31357
31358    let editor = cx.add_window(|window, cx| {
31359        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31360        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31361        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31362    });
31363
31364    // Initially no drag state
31365    editor
31366        .update(cx, |editor, _window, _cx| {
31367            assert!(editor.diff_review_drag_state.is_none());
31368        })
31369        .unwrap();
31370
31371    // Start drag at row 1
31372    editor
31373        .update(cx, |editor, window, cx| {
31374            editor.start_diff_review_drag(DisplayRow(1), window, cx);
31375        })
31376        .unwrap();
31377
31378    // Verify drag state is set
31379    editor
31380        .update(cx, |editor, window, cx| {
31381            assert!(editor.diff_review_drag_state.is_some());
31382            let snapshot = editor.snapshot(window, cx);
31383            let range = editor
31384                .diff_review_drag_state
31385                .as_ref()
31386                .unwrap()
31387                .row_range(&snapshot.display_snapshot);
31388            assert_eq!(*range.start(), DisplayRow(1));
31389            assert_eq!(*range.end(), DisplayRow(1));
31390        })
31391        .unwrap();
31392
31393    // Update drag to row 3
31394    editor
31395        .update(cx, |editor, window, cx| {
31396            editor.update_diff_review_drag(DisplayRow(3), window, cx);
31397        })
31398        .unwrap();
31399
31400    // Verify drag state is updated
31401    editor
31402        .update(cx, |editor, window, cx| {
31403            assert!(editor.diff_review_drag_state.is_some());
31404            let snapshot = editor.snapshot(window, cx);
31405            let range = editor
31406                .diff_review_drag_state
31407                .as_ref()
31408                .unwrap()
31409                .row_range(&snapshot.display_snapshot);
31410            assert_eq!(*range.start(), DisplayRow(1));
31411            assert_eq!(*range.end(), DisplayRow(3));
31412        })
31413        .unwrap();
31414
31415    // End drag - should show overlay
31416    editor
31417        .update(cx, |editor, window, cx| {
31418            editor.end_diff_review_drag(window, cx);
31419        })
31420        .unwrap();
31421
31422    // Verify drag state is cleared and overlay is shown
31423    editor
31424        .update(cx, |editor, _window, cx| {
31425            assert!(editor.diff_review_drag_state.is_none());
31426            assert!(!editor.diff_review_overlays.is_empty());
31427            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31428        })
31429        .unwrap();
31430}
31431
31432#[gpui::test]
31433fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
31434    init_test(cx, |_| {});
31435
31436    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31437
31438    // Start drag
31439    editor
31440        .update(cx, |editor, window, cx| {
31441            editor.start_diff_review_drag(DisplayRow(0), window, cx);
31442        })
31443        .unwrap();
31444
31445    // Verify drag state is set
31446    editor
31447        .update(cx, |editor, _window, _cx| {
31448            assert!(editor.diff_review_drag_state.is_some());
31449        })
31450        .unwrap();
31451
31452    // Cancel drag
31453    editor
31454        .update(cx, |editor, _window, cx| {
31455            editor.cancel_diff_review_drag(cx);
31456        })
31457        .unwrap();
31458
31459    // Verify drag state is cleared and no overlay was created
31460    editor
31461        .update(cx, |editor, _window, _cx| {
31462            assert!(editor.diff_review_drag_state.is_none());
31463            assert!(editor.diff_review_overlays.is_empty());
31464        })
31465        .unwrap();
31466}
31467
31468#[gpui::test]
31469fn test_calculate_overlay_height(cx: &mut TestAppContext) {
31470    init_test(cx, |_| {});
31471
31472    // This test verifies that calculate_overlay_height returns correct heights
31473    // based on comment count and expanded state.
31474    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31475
31476    _ = editor.update(cx, |editor, _window, cx| {
31477        let snapshot = editor.buffer().read(cx).snapshot(cx);
31478        let anchor = snapshot.anchor_before(Point::new(0, 0));
31479        let key = DiffHunkKey {
31480            file_path: Arc::from(util::rel_path::RelPath::empty()),
31481            hunk_start_anchor: anchor,
31482        };
31483
31484        // No comments: base height of 2
31485        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
31486        assert_eq!(
31487            height_no_comments, 2,
31488            "Base height should be 2 with no comments"
31489        );
31490
31491        // Add one comment
31492        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
31493
31494        let snapshot = editor.buffer().read(cx).snapshot(cx);
31495
31496        // With comments expanded: base (2) + header (1) + 2 per comment
31497        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31498        assert_eq!(
31499            height_expanded,
31500            2 + 1 + 2, // base + header + 1 comment * 2
31501            "Height with 1 comment expanded"
31502        );
31503
31504        // With comments collapsed: base (2) + header (1)
31505        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31506        assert_eq!(
31507            height_collapsed,
31508            2 + 1, // base + header only
31509            "Height with comments collapsed"
31510        );
31511
31512        // Add more comments
31513        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
31514        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
31515
31516        let snapshot = editor.buffer().read(cx).snapshot(cx);
31517
31518        // With 3 comments expanded
31519        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31520        assert_eq!(
31521            height_3_expanded,
31522            2 + 1 + (3 * 2), // base + header + 3 comments * 2
31523            "Height with 3 comments expanded"
31524        );
31525
31526        // Collapsed height stays the same regardless of comment count
31527        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31528        assert_eq!(
31529            height_3_collapsed,
31530            2 + 1, // base + header only
31531            "Height with 3 comments collapsed should be same as 1 comment collapsed"
31532        );
31533    });
31534}
31535
31536#[gpui::test]
31537async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
31538    init_test(cx, |_| {});
31539
31540    let language = Arc::new(Language::new(
31541        LanguageConfig::default(),
31542        Some(tree_sitter_rust::LANGUAGE.into()),
31543    ));
31544
31545    let text = r#"
31546        fn main() {
31547            let x = foo(1, 2);
31548        }
31549    "#
31550    .unindent();
31551
31552    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31553    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31554    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31555
31556    editor
31557        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31558        .await;
31559
31560    // Test case 1: Move to end of syntax nodes
31561    editor.update_in(cx, |editor, window, cx| {
31562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31563            s.select_display_ranges([
31564                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
31565            ]);
31566        });
31567    });
31568    editor.update(cx, |editor, cx| {
31569        assert_text_with_selections(
31570            editor,
31571            indoc! {r#"
31572                fn main() {
31573                    let x = foo(ˇ1, 2);
31574                }
31575            "#},
31576            cx,
31577        );
31578    });
31579    editor.update_in(cx, |editor, window, cx| {
31580        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31581    });
31582    editor.update(cx, |editor, cx| {
31583        assert_text_with_selections(
31584            editor,
31585            indoc! {r#"
31586                fn main() {
31587                    let x = foo(1ˇ, 2);
31588                }
31589            "#},
31590            cx,
31591        );
31592    });
31593    editor.update_in(cx, |editor, window, cx| {
31594        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31595    });
31596    editor.update(cx, |editor, cx| {
31597        assert_text_with_selections(
31598            editor,
31599            indoc! {r#"
31600                fn main() {
31601                    let x = foo(1, 2)ˇ;
31602                }
31603            "#},
31604            cx,
31605        );
31606    });
31607    editor.update_in(cx, |editor, window, cx| {
31608        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31609    });
31610    editor.update(cx, |editor, cx| {
31611        assert_text_with_selections(
31612            editor,
31613            indoc! {r#"
31614                fn main() {
31615                    let x = foo(1, 2);ˇ
31616                }
31617            "#},
31618            cx,
31619        );
31620    });
31621    editor.update_in(cx, |editor, window, cx| {
31622        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31623    });
31624    editor.update(cx, |editor, cx| {
31625        assert_text_with_selections(
31626            editor,
31627            indoc! {r#"
31628                fn main() {
31629                    let x = foo(1, 2);
3163031631            "#},
31632            cx,
31633        );
31634    });
31635
31636    // Test case 2: Move to start of syntax nodes
31637    editor.update_in(cx, |editor, window, cx| {
31638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31639            s.select_display_ranges([
31640                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
31641            ]);
31642        });
31643    });
31644    editor.update(cx, |editor, cx| {
31645        assert_text_with_selections(
31646            editor,
31647            indoc! {r#"
31648                fn main() {
31649                    let x = foo(1, 2ˇ);
31650                }
31651            "#},
31652            cx,
31653        );
31654    });
31655    editor.update_in(cx, |editor, window, cx| {
31656        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31657    });
31658    editor.update(cx, |editor, cx| {
31659        assert_text_with_selections(
31660            editor,
31661            indoc! {r#"
31662                fn main() {
31663                    let x = fooˇ(1, 2);
31664                }
31665            "#},
31666            cx,
31667        );
31668    });
31669    editor.update_in(cx, |editor, window, cx| {
31670        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31671    });
31672    editor.update(cx, |editor, cx| {
31673        assert_text_with_selections(
31674            editor,
31675            indoc! {r#"
31676                fn main() {
31677                    let x = ˇfoo(1, 2);
31678                }
31679            "#},
31680            cx,
31681        );
31682    });
31683    editor.update_in(cx, |editor, window, cx| {
31684        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31685    });
31686    editor.update(cx, |editor, cx| {
31687        assert_text_with_selections(
31688            editor,
31689            indoc! {r#"
31690                fn main() {
31691                    ˇlet x = foo(1, 2);
31692                }
31693            "#},
31694            cx,
31695        );
31696    });
31697    editor.update_in(cx, |editor, window, cx| {
31698        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31699    });
31700    editor.update(cx, |editor, cx| {
31701        assert_text_with_selections(
31702            editor,
31703            indoc! {r#"
31704                fn main() ˇ{
31705                    let x = foo(1, 2);
31706                }
31707            "#},
31708            cx,
31709        );
31710    });
31711    editor.update_in(cx, |editor, window, cx| {
31712        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31713    });
31714    editor.update(cx, |editor, cx| {
31715        assert_text_with_selections(
31716            editor,
31717            indoc! {r#"
31718                ˇfn main() {
31719                    let x = foo(1, 2);
31720                }
31721            "#},
31722            cx,
31723        );
31724    });
31725}
31726
31727#[gpui::test]
31728async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
31729    init_test(cx, |_| {});
31730
31731    let language = Arc::new(Language::new(
31732        LanguageConfig::default(),
31733        Some(tree_sitter_rust::LANGUAGE.into()),
31734    ));
31735
31736    let text = r#"
31737        fn main() {
31738            let x = foo(1, 2);
31739            let y = bar(3, 4);
31740        }
31741    "#
31742    .unindent();
31743
31744    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31745    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31746    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31747
31748    editor
31749        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31750        .await;
31751
31752    // Test case 1: Move to end of syntax nodes with two cursors
31753    editor.update_in(cx, |editor, window, cx| {
31754        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31755            s.select_display_ranges([
31756                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
31757                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
31758            ]);
31759        });
31760    });
31761    editor.update(cx, |editor, cx| {
31762        assert_text_with_selections(
31763            editor,
31764            indoc! {r#"
31765                fn main() {
31766                    let x = foo(1, 2ˇ);
31767                    let y = bar(3, 4ˇ);
31768                }
31769            "#},
31770            cx,
31771        );
31772    });
31773    editor.update_in(cx, |editor, window, cx| {
31774        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31775    });
31776    editor.update(cx, |editor, cx| {
31777        assert_text_with_selections(
31778            editor,
31779            indoc! {r#"
31780                fn main() {
31781                    let x = foo(1, 2)ˇ;
31782                    let y = bar(3, 4)ˇ;
31783                }
31784            "#},
31785            cx,
31786        );
31787    });
31788    editor.update_in(cx, |editor, window, cx| {
31789        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31790    });
31791    editor.update(cx, |editor, cx| {
31792        assert_text_with_selections(
31793            editor,
31794            indoc! {r#"
31795                fn main() {
31796                    let x = foo(1, 2);ˇ
31797                    let y = bar(3, 4);ˇ
31798                }
31799            "#},
31800            cx,
31801        );
31802    });
31803
31804    // Test case 2: Move to start of syntax nodes with two cursors
31805    editor.update_in(cx, |editor, window, cx| {
31806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31807            s.select_display_ranges([
31808                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
31809                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
31810            ]);
31811        });
31812    });
31813    editor.update(cx, |editor, cx| {
31814        assert_text_with_selections(
31815            editor,
31816            indoc! {r#"
31817                fn main() {
31818                    let x = foo(1, ˇ2);
31819                    let y = bar(3, ˇ4);
31820                }
31821            "#},
31822            cx,
31823        );
31824    });
31825    editor.update_in(cx, |editor, window, cx| {
31826        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31827    });
31828    editor.update(cx, |editor, cx| {
31829        assert_text_with_selections(
31830            editor,
31831            indoc! {r#"
31832                fn main() {
31833                    let x = fooˇ(1, 2);
31834                    let y = barˇ(3, 4);
31835                }
31836            "#},
31837            cx,
31838        );
31839    });
31840    editor.update_in(cx, |editor, window, cx| {
31841        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31842    });
31843    editor.update(cx, |editor, cx| {
31844        assert_text_with_selections(
31845            editor,
31846            indoc! {r#"
31847                fn main() {
31848                    let x = ˇfoo(1, 2);
31849                    let y = ˇbar(3, 4);
31850                }
31851            "#},
31852            cx,
31853        );
31854    });
31855    editor.update_in(cx, |editor, window, cx| {
31856        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31857    });
31858    editor.update(cx, |editor, cx| {
31859        assert_text_with_selections(
31860            editor,
31861            indoc! {r#"
31862                fn main() {
31863                    ˇlet x = foo(1, 2);
31864                    ˇlet y = bar(3, 4);
31865                }
31866            "#},
31867            cx,
31868        );
31869    });
31870}
31871
31872#[gpui::test]
31873async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
31874    cx: &mut TestAppContext,
31875) {
31876    init_test(cx, |_| {});
31877
31878    let language = Arc::new(Language::new(
31879        LanguageConfig::default(),
31880        Some(tree_sitter_rust::LANGUAGE.into()),
31881    ));
31882
31883    let text = r#"
31884        fn main() {
31885            let x = foo(1, 2);
31886            let msg = "hello world";
31887        }
31888    "#
31889    .unindent();
31890
31891    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31892    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31893    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31894
31895    editor
31896        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31897        .await;
31898
31899    // Test case 1: With existing selection, move_to_end keeps selection
31900    editor.update_in(cx, |editor, window, cx| {
31901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31902            s.select_display_ranges([
31903                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
31904            ]);
31905        });
31906    });
31907    editor.update(cx, |editor, cx| {
31908        assert_text_with_selections(
31909            editor,
31910            indoc! {r#"
31911                fn main() {
31912                    let x = «foo(1, 2)ˇ»;
31913                    let msg = "hello world";
31914                }
31915            "#},
31916            cx,
31917        );
31918    });
31919    editor.update_in(cx, |editor, window, cx| {
31920        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31921    });
31922    editor.update(cx, |editor, cx| {
31923        assert_text_with_selections(
31924            editor,
31925            indoc! {r#"
31926                fn main() {
31927                    let x = «foo(1, 2)ˇ»;
31928                    let msg = "hello world";
31929                }
31930            "#},
31931            cx,
31932        );
31933    });
31934
31935    // Test case 2: Move to end within a string
31936    editor.update_in(cx, |editor, window, cx| {
31937        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31938            s.select_display_ranges([
31939                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
31940            ]);
31941        });
31942    });
31943    editor.update(cx, |editor, cx| {
31944        assert_text_with_selections(
31945            editor,
31946            indoc! {r#"
31947                fn main() {
31948                    let x = foo(1, 2);
31949                    let msg = "ˇhello world";
31950                }
31951            "#},
31952            cx,
31953        );
31954    });
31955    editor.update_in(cx, |editor, window, cx| {
31956        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31957    });
31958    editor.update(cx, |editor, cx| {
31959        assert_text_with_selections(
31960            editor,
31961            indoc! {r#"
31962                fn main() {
31963                    let x = foo(1, 2);
31964                    let msg = "hello worldˇ";
31965                }
31966            "#},
31967            cx,
31968        );
31969    });
31970
31971    // Test case 3: Move to start within a string
31972    editor.update_in(cx, |editor, window, cx| {
31973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31974            s.select_display_ranges([
31975                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
31976            ]);
31977        });
31978    });
31979    editor.update(cx, |editor, cx| {
31980        assert_text_with_selections(
31981            editor,
31982            indoc! {r#"
31983                fn main() {
31984                    let x = foo(1, 2);
31985                    let msg = "hello ˇworld";
31986                }
31987            "#},
31988            cx,
31989        );
31990    });
31991    editor.update_in(cx, |editor, window, cx| {
31992        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31993    });
31994    editor.update(cx, |editor, cx| {
31995        assert_text_with_selections(
31996            editor,
31997            indoc! {r#"
31998                fn main() {
31999                    let x = foo(1, 2);
32000                    let msg = "ˇhello world";
32001                }
32002            "#},
32003            cx,
32004        );
32005    });
32006}
32007
32008#[gpui::test]
32009async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
32010    init_test(cx, |_| {});
32011
32012    let language = Arc::new(Language::new(
32013        LanguageConfig::default(),
32014        Some(tree_sitter_rust::LANGUAGE.into()),
32015    ));
32016
32017    // Test Group 1.1: Cursor in String - First Jump (Select to End)
32018    let text = r#"let msg = "foo bar baz";"#.unindent();
32019
32020    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32021    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32022    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32023
32024    editor
32025        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32026        .await;
32027
32028    editor.update_in(cx, |editor, window, cx| {
32029        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32030            s.select_display_ranges([
32031                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
32032            ]);
32033        });
32034    });
32035    editor.update(cx, |editor, cx| {
32036        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
32037    });
32038    editor.update_in(cx, |editor, window, cx| {
32039        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32040    });
32041    editor.update(cx, |editor, cx| {
32042        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
32043    });
32044
32045    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
32046    editor.update_in(cx, |editor, window, cx| {
32047        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32048    });
32049    editor.update(cx, |editor, cx| {
32050        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
32051    });
32052
32053    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
32054    editor.update_in(cx, |editor, window, cx| {
32055        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32056    });
32057    editor.update(cx, |editor, cx| {
32058        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
32059    });
32060
32061    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
32062    editor.update_in(cx, |editor, window, cx| {
32063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32064            s.select_display_ranges([
32065                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
32066            ]);
32067        });
32068    });
32069    editor.update(cx, |editor, cx| {
32070        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
32071    });
32072    editor.update_in(cx, |editor, window, cx| {
32073        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32074    });
32075    editor.update(cx, |editor, cx| {
32076        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
32077    });
32078
32079    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
32080    editor.update_in(cx, |editor, window, cx| {
32081        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32082    });
32083    editor.update(cx, |editor, cx| {
32084        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
32085    });
32086
32087    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
32088    editor.update_in(cx, |editor, window, cx| {
32089        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32090    });
32091    editor.update(cx, |editor, cx| {
32092        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
32093    });
32094
32095    // Test Group 2.1: Let Statement Progression (Select to End)
32096    let text = r#"
32097fn main() {
32098    let x = "hello";
32099}
32100"#
32101    .unindent();
32102
32103    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32104    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32105    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32106
32107    editor
32108        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32109        .await;
32110
32111    editor.update_in(cx, |editor, window, cx| {
32112        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32113            s.select_display_ranges([
32114                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
32115            ]);
32116        });
32117    });
32118    editor.update(cx, |editor, cx| {
32119        assert_text_with_selections(
32120            editor,
32121            indoc! {r#"
32122                fn main() {
32123                    let xˇ = "hello";
32124                }
32125            "#},
32126            cx,
32127        );
32128    });
32129    editor.update_in(cx, |editor, window, cx| {
32130        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32131    });
32132    editor.update(cx, |editor, cx| {
32133        assert_text_with_selections(
32134            editor,
32135            indoc! {r##"
32136                fn main() {
32137                    let x« = "hello";ˇ»
32138                }
32139            "##},
32140            cx,
32141        );
32142    });
32143    editor.update_in(cx, |editor, window, cx| {
32144        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32145    });
32146    editor.update(cx, |editor, cx| {
32147        assert_text_with_selections(
32148            editor,
32149            indoc! {r#"
32150                fn main() {
32151                    let x« = "hello";
32152                }ˇ»
32153            "#},
32154            cx,
32155        );
32156    });
32157
32158    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
32159    let text = r#"let x = "hello";"#.unindent();
32160
32161    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32162    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32163    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32164
32165    editor
32166        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32167        .await;
32168
32169    editor.update_in(cx, |editor, window, cx| {
32170        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32171            s.select_display_ranges([
32172                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
32173            ]);
32174        });
32175    });
32176    editor.update(cx, |editor, cx| {
32177        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
32178    });
32179    editor.update_in(cx, |editor, window, cx| {
32180        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32181    });
32182    editor.update(cx, |editor, cx| {
32183        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
32184    });
32185
32186    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
32187    editor.update_in(cx, |editor, window, cx| {
32188        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32189            s.select_display_ranges([
32190                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
32191            ]);
32192        });
32193    });
32194    editor.update(cx, |editor, cx| {
32195        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
32196    });
32197    editor.update_in(cx, |editor, window, cx| {
32198        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32199    });
32200    editor.update(cx, |editor, cx| {
32201        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
32202    });
32203
32204    // Test Group 3.1: Create Selection from Cursor (Select to End)
32205    let text = r#"let x = "hello world";"#.unindent();
32206
32207    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32208    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32209    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32210
32211    editor
32212        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32213        .await;
32214
32215    editor.update_in(cx, |editor, window, cx| {
32216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32217            s.select_display_ranges([
32218                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
32219            ]);
32220        });
32221    });
32222    editor.update(cx, |editor, cx| {
32223        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
32224    });
32225    editor.update_in(cx, |editor, window, cx| {
32226        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32227    });
32228    editor.update(cx, |editor, cx| {
32229        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
32230    });
32231
32232    // Test Group 3.2: Extend Existing Selection (Select to End)
32233    editor.update_in(cx, |editor, window, cx| {
32234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32235            s.select_display_ranges([
32236                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
32237            ]);
32238        });
32239    });
32240    editor.update(cx, |editor, cx| {
32241        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
32242    });
32243    editor.update_in(cx, |editor, window, cx| {
32244        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32245    });
32246    editor.update(cx, |editor, cx| {
32247        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
32248    });
32249
32250    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
32251    let text = r#"let x = "hello"; let y = 42;"#.unindent();
32252
32253    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32254    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32255    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32256
32257    editor
32258        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32259        .await;
32260
32261    editor.update_in(cx, |editor, window, cx| {
32262        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32263            s.select_display_ranges([
32264                // Cursor inside string content
32265                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
32266                // Cursor at let statement semicolon
32267                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
32268                // Cursor inside integer literal
32269                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
32270            ]);
32271        });
32272    });
32273    editor.update(cx, |editor, cx| {
32274        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
32275    });
32276    editor.update_in(cx, |editor, window, cx| {
32277        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32278    });
32279    editor.update(cx, |editor, cx| {
32280        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
32281    });
32282
32283    // Test Group 4.2: Multiple Cursors on Separate Lines
32284    let text = r#"
32285let x = "hello";
32286let y = 42;
32287"#
32288    .unindent();
32289
32290    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32291    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32292    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32293
32294    editor
32295        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32296        .await;
32297
32298    editor.update_in(cx, |editor, window, cx| {
32299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32300            s.select_display_ranges([
32301                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
32302                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
32303            ]);
32304        });
32305    });
32306
32307    editor.update(cx, |editor, cx| {
32308        assert_text_with_selections(
32309            editor,
32310            indoc! {r#"
32311                let x = "helˇlo";
32312                let y = 4ˇ2;
32313            "#},
32314            cx,
32315        );
32316    });
32317    editor.update_in(cx, |editor, window, cx| {
32318        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32319    });
32320    editor.update(cx, |editor, cx| {
32321        assert_text_with_selections(
32322            editor,
32323            indoc! {r#"
32324                let x = "hel«loˇ»";
32325                let y = 4«2ˇ»;
32326            "#},
32327            cx,
32328        );
32329    });
32330
32331    // Test Group 5.1: Nested Function Calls
32332    let text = r#"let result = foo(bar("arg"));"#.unindent();
32333
32334    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32335    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32336    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32337
32338    editor
32339        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32340        .await;
32341
32342    editor.update_in(cx, |editor, window, cx| {
32343        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32344            s.select_display_ranges([
32345                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
32346            ]);
32347        });
32348    });
32349    editor.update(cx, |editor, cx| {
32350        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
32351    });
32352    editor.update_in(cx, |editor, window, cx| {
32353        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32354    });
32355    editor.update(cx, |editor, cx| {
32356        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
32357    });
32358    editor.update_in(cx, |editor, window, cx| {
32359        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32360    });
32361    editor.update(cx, |editor, cx| {
32362        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
32363    });
32364    editor.update_in(cx, |editor, window, cx| {
32365        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32366    });
32367    editor.update(cx, |editor, cx| {
32368        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
32369    });
32370
32371    // Test Group 6.1: Block Comments
32372    let text = r#"let x = /* multi
32373                             line
32374                             comment */;"#
32375        .unindent();
32376
32377    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32378    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32379    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32380
32381    editor
32382        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32383        .await;
32384
32385    editor.update_in(cx, |editor, window, cx| {
32386        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32387            s.select_display_ranges([
32388                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
32389            ]);
32390        });
32391    });
32392    editor.update(cx, |editor, cx| {
32393        assert_text_with_selections(
32394            editor,
32395            indoc! {r#"
32396let x = /* multiˇ
32397line
32398comment */;"#},
32399            cx,
32400        );
32401    });
32402    editor.update_in(cx, |editor, window, cx| {
32403        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32404    });
32405    editor.update(cx, |editor, cx| {
32406        assert_text_with_selections(
32407            editor,
32408            indoc! {r#"
32409let x = /* multi«
32410line
32411comment */ˇ»;"#},
32412            cx,
32413        );
32414    });
32415
32416    // Test Group 6.2: Array/Vector Literals
32417    let text = r#"let arr = [1, 2, 3];"#.unindent();
32418
32419    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32420    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32421    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32422
32423    editor
32424        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32425        .await;
32426
32427    editor.update_in(cx, |editor, window, cx| {
32428        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32429            s.select_display_ranges([
32430                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
32431            ]);
32432        });
32433    });
32434    editor.update(cx, |editor, cx| {
32435        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
32436    });
32437    editor.update_in(cx, |editor, window, cx| {
32438        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32439    });
32440    editor.update(cx, |editor, cx| {
32441        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
32442    });
32443    editor.update_in(cx, |editor, window, cx| {
32444        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32445    });
32446    editor.update(cx, |editor, cx| {
32447        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
32448    });
32449}